From ac12ca4b7db4eb57a2a214a8e30baa8862c95d82 Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 21 Jun 2023 12:46:25 -0300 Subject: [PATCH 01/27] (feat) Created the API component for the auction module (with the unit tests) --- pyinjective/__init__.py | 2 +- pyinjective/client/__init__.py | 0 pyinjective/client/chain/__init__.py | 0 pyinjective/client/chain/grpc/__init__.py | 0 .../chain/grpc/chain_grpc_auction_api.py | 66 +++++++++ pyinjective/{client.py => sync_client.py} | 2 +- pyinjective/transaction.py | 4 +- tests/client/__init__.py | 0 tests/client/chain/__init__.py | 0 tests/client/chain/grpc/__init__.py | 0 .../configurable_auction_query_servicer.py | 24 ++++ .../chain/grpc/test_chain_grpc_auction_api.py | 131 ++++++++++++++++++ 12 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 pyinjective/client/__init__.py create mode 100644 pyinjective/client/chain/__init__.py create mode 100644 pyinjective/client/chain/grpc/__init__.py create mode 100644 pyinjective/client/chain/grpc/chain_grpc_auction_api.py rename pyinjective/{client.py => sync_client.py} (99%) create mode 100644 tests/client/__init__.py create mode 100644 tests/client/chain/__init__.py create mode 100644 tests/client/chain/grpc/__init__.py create mode 100644 tests/client/chain/grpc/configurable_auction_query_servicer.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_auction_api.py diff --git a/pyinjective/__init__.py b/pyinjective/__init__.py index e17ef7b1..0c579588 100644 --- a/pyinjective/__init__.py +++ b/pyinjective/__init__.py @@ -1,3 +1,3 @@ -from .client import Client +from .sync_client import SyncClient from .transaction import Transaction from .wallet import PrivateKey, PublicKey, Address diff --git a/pyinjective/client/__init__.py b/pyinjective/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/__init__.py b/pyinjective/client/chain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/grpc/__init__.py b/pyinjective/client/chain/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/grpc/chain_grpc_auction_api.py b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py new file mode 100644 index 00000000..1e54cc27 --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py @@ -0,0 +1,66 @@ +from typing import Dict, Any + +from grpc.aio import Channel + +from pyinjective.proto.injective.auction.v1beta1 import ( + query_pb2_grpc as auction_query_grpc, + query_pb2 as auction_query_pb, +) + + +class ChainGrpcAuctionApi: + + def __init__(self, channel: Channel): + self._stub = auction_query_grpc.QueryStub(channel) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = auction_query_pb.QueryAuctionParamsRequest() + response = await self._stub.AuctionParams(request) + + module_params = { + "auction_period": response.params.auction_period, + "min_next_bid_increment_rate": response.params.min_next_bid_increment_rate, + } + + return module_params + + async def fetch_module_state(self) -> Dict[str, Any]: + request = auction_query_pb.QueryModuleStateRequest() + response = await self._stub.AuctionModuleState(request) + + if response.state.highest_bid is None: + highest_bid = { + "bidder": "", + "amount": "", + } + else: + highest_bid = { + "bidder": response.state.highest_bid.bidder, + "amount": response.state.highest_bid.amount, + } + + module_state = { + "params": { + "auction_period": response.state.params.auction_period, + "min_next_bid_increment_rate": response.state.params.min_next_bid_increment_rate, + }, + "auction_round": response.state.auction_round, + "highest_bid": highest_bid, + "auction_ending_timestamp": response.state.auction_ending_timestamp, + } + + return module_state + + async def fetch_current_basket(self) -> Dict[str, Any]: + request = auction_query_pb.QueryCurrentAuctionBasketRequest() + response = await self._stub.CurrentAuctionBasket(request) + + current_basket = { + "amount_list": [{"amount": coin.amount, "denom": coin.denom} for coin in response.amount], + "auction_round": response.auctionRound, + "auction_closing_time": response.auctionClosingTime, + "highest_bidder": response.highestBidder, + "highest_bid_amount": response.highestBidAmount, + } + + return current_basket diff --git a/pyinjective/client.py b/pyinjective/sync_client.py similarity index 99% rename from pyinjective/client.py rename to pyinjective/sync_client.py index 10b82329..5045420e 100644 --- a/pyinjective/client.py +++ b/pyinjective/sync_client.py @@ -54,7 +54,7 @@ DEFAULT_SESSION_RENEWAL_OFFSET = 120 # seconds DEFAULT_BLOCK_TIME = 3 # seconds -class Client: +class SyncClient: def __init__( self, network: Network, diff --git a/pyinjective/transaction.py b/pyinjective/transaction.py index 0a83ec9e..59d5e159 100644 --- a/pyinjective/transaction.py +++ b/pyinjective/transaction.py @@ -5,7 +5,7 @@ from .proto.cosmos.tx.v1beta1 import tx_pb2 as cosmos_tx_type from .proto.cosmos.tx.signing.v1beta1 import signing_pb2 as tx_sign -from .client import Client +from .sync_client import SyncClient from .constant import MAX_MEMO_CHARACTERS from .exceptions import EmptyMsgError, NotFoundError, UndefinedError, ValueTooLargeError from .wallet import PublicKey @@ -44,7 +44,7 @@ def with_messages(self, *msgs: message.Message) -> "Transaction": self.msgs.extend(self.__convert_msgs(msgs)) return self - def with_sender(self, client: Client, sender: str) -> "Transaction": + def with_sender(self, client: SyncClient, sender: str) -> "Transaction": if len(self.msgs) == 0: raise EmptyMsgError("messsage is empty, please use with_messages at least 1 message") account = client.get_account(sender) diff --git a/tests/client/__init__.py b/tests/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/__init__.py b/tests/client/chain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/grpc/__init__.py b/tests/client/chain/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/grpc/configurable_auction_query_servicer.py b/tests/client/chain/grpc/configurable_auction_query_servicer.py new file mode 100644 index 00000000..aa7ba51a --- /dev/null +++ b/tests/client/chain/grpc/configurable_auction_query_servicer.py @@ -0,0 +1,24 @@ +from collections import deque + +from pyinjective.proto.injective.auction.v1beta1 import ( + query_pb2_grpc as auction_query_grpc, + query_pb2 as auction_query_pb, +) + + +class ConfigurableAuctionQueryServicer(auction_query_grpc.QueryServicer): + + def __init__(self): + super().__init__() + self.auction_params = deque() + self.module_states = deque() + self.current_baskets = deque() + + async def AuctionParams(self, request: auction_query_pb.QueryAuctionParamsRequest, context=None): + return self.auction_params.pop() + + async def AuctionModuleState(self, request: auction_query_pb.QueryModuleStateRequest, context=None): + return self.module_states.pop() + + async def CurrentAuctionBasket(self, request: auction_query_pb.QueryCurrentAuctionBasketRequest, context=None): + return self.current_baskets.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_auction_api.py b/tests/client/chain/grpc/test_chain_grpc_auction_api.py new file mode 100644 index 00000000..39912c0f --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_auction_api.py @@ -0,0 +1,131 @@ +import grpc +import pytest + +from pyinjective.client.chain.grpc.chain_grpc_auction_api import ChainGrpcAuctionApi +from pyinjective.constant import Network +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.injective.auction.v1beta1 import ( + auction_pb2 as auction_pb, + genesis_pb2 as genesis_pb, + query_pb2 as auction_query_pb, +) +from tests.client.chain.grpc.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer + + +@pytest.fixture +def auction_servicer(): + return ConfigurableAuctionQueryServicer() + + +class TestChainGrpcAuctionApi: + + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + auction_servicer, + ): + params = auction_pb.Params( + auction_period=604800, + min_next_bid_increment_rate="2500000000000000" + ) + auction_servicer.auction_params.append(auction_query_pb.QueryAuctionParamsResponse( + params=params + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel) + api._stub = auction_servicer + + module_params = await api.fetch_module_params() + expected_params = { + "auction_period": 604800, + "min_next_bid_increment_rate": "2500000000000000", + } + + assert (expected_params == module_params) + + @pytest.mark.asyncio + async def test_fetch_module_state( + self, + auction_servicer, + ): + params = auction_pb.Params( + auction_period=604800, + min_next_bid_increment_rate="2500000000000000" + ) + highest_bid = auction_pb.Bid( + bidder="inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + amount="\n\003inj\022\0232347518723906280000", + ) + state = genesis_pb.GenesisState( + params=params, + auction_round=50, + highest_bid=highest_bid, + auction_ending_timestamp=1687504387, + ) + auction_servicer.module_states.append(auction_query_pb.QueryModuleStateResponse( + state=state + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel) + api._stub = auction_servicer + + module_state = await api.fetch_module_state() + expected_state = { + "params": { + "auction_period": 604800, + "min_next_bid_increment_rate": "2500000000000000", + }, + "auction_round": 50, + "highest_bid": { + "bidder": "inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + "amount": "\n\003inj\022\0232347518723906280000", + }, + "auction_ending_timestamp": 1687504387, + } + + assert (expected_state == module_state) + + @pytest.mark.asyncio + async def test_fetch_current_basket( + self, + auction_servicer, + ): + first_amount = coin_pb.Coin( + amount="15059786755", + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + ) + second_amount = coin_pb.Coin( + amount="200000", + denom="peggy0xf9152067989BDc8783fF586624124C05A529A5D1", + ) + + auction_servicer.current_baskets.append(auction_query_pb.QueryCurrentAuctionBasketResponse( + amount=[first_amount, second_amount], + auctionRound=50, + auctionClosingTime=1687504387, + highestBidder="inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + highestBidAmount="2347518723906280000", + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel) + api._stub = auction_servicer + + current_basket = await api.fetch_current_basket() + expected_basket = { + "amount_list": [{"amount": coin.amount, "denom": coin.denom} for coin in [first_amount, second_amount]], + "auction_round": 50, + "auction_closing_time": 1687504387, + "highest_bidder": "inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + "highest_bid_amount": "2347518723906280000", + } + + assert (expected_basket == current_basket) From cf4dc617bb73ea74baabe95da4db13b686863f7b Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 21 Jun 2023 23:58:22 -0300 Subject: [PATCH 02/27] (feat) Created the API class for the bank module (with its unit tests) --- pyinjective/async_client.py | 11 +- .../client/chain/grpc/chain_grpc_bank_api.py | 58 ++++++ .../grpc/configurable_bank_query_servicer.py | 28 +++ .../chain/grpc/test_chain_grpc_auction_api.py | 40 ++++ .../chain/grpc/test_chain_grpc_bank_api.py | 179 ++++++++++++++++++ 5 files changed, 309 insertions(+), 7 deletions(-) create mode 100644 pyinjective/client/chain/grpc/chain_grpc_bank_api.py create mode 100644 tests/client/chain/grpc/configurable_bank_query_servicer.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_bank_api.py diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 429b73ef..562914b2 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -12,6 +12,7 @@ from pyinjective.composer import Composer from . import constant +from .client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi from .core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket from .core.token import Token from .exceptions import NotFoundError @@ -123,7 +124,7 @@ def __init__( ) self.stubAuth = auth_query_grpc.QueryStub(self.chain_channel) self.stubAuthz = authz_query_grpc.QueryStub(self.chain_channel) - self.stubBank = bank_query_grpc.QueryStub(self.chain_channel) + self.bank_api = ChainGrpcBankApi(channel=self.chain_channel) self.stubTx = tx_service_grpc.ServiceStub(self.chain_channel) # attempt to load from disk @@ -422,14 +423,10 @@ async def get_grants(self, granter: str, grantee: str, **kwargs): ) async def get_bank_balances(self, address: str): - return await self.stubBank.AllBalances( - bank_query.QueryAllBalancesRequest(address=address) - ) + return await self.bank_api.fetch_balances(account_address=address) async def get_bank_balance(self, address: str, denom: str): - return await self.stubBank.Balance( - bank_query.QueryBalanceRequest(address=address, denom=denom) - ) + return await self.bank_api.fetch_balance(account_address=address, denom=denom) # Injective Exchange client methods diff --git a/pyinjective/client/chain/grpc/chain_grpc_bank_api.py b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py new file mode 100644 index 00000000..7e916fba --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py @@ -0,0 +1,58 @@ +from typing import Dict, Any + +from grpc.aio import Channel + +from pyinjective.proto.cosmos.bank.v1beta1 import ( + query_pb2_grpc as bank_query_grpc, + query_pb2 as bank_query_pb, +) + +class ChainGrpcBankApi: + + def __init__(self, channel: Channel): + self._stub = bank_query_grpc.QueryStub(channel) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = bank_query_pb.QueryParamsRequest() + response = await self._stub.Params(request) + + module_params = { + "default_send_enabled": response.params.default_send_enabled, + } + + return module_params + + async def fetch_balance(self, account_address: str, denom: str) -> Dict[str, Any]: + request = bank_query_pb.QueryBalanceRequest(address=account_address, denom=denom) + response = await self._stub.Balance(request) + + bank_balance = { + "amount": response.balance.amount, + "denom": response.balance.denom, + } + + return bank_balance + + async def fetch_balances(self, account_address: str) -> Dict[str, Any]: + request = bank_query_pb.QueryAllBalancesRequest(address=account_address) + response = await self._stub.AllBalances(request) + + bank_balances = { + "balances": [{"amount": coin.amount, "denom": coin.denom} for coin in response.balances], + "pagination": {"total": response.pagination.total}} + + return bank_balances + + async def fetch_total_supply(self) -> Dict[str, Any]: + request = bank_query_pb.QueryTotalSupplyRequest() + response = await self._stub.TotalSupply(request) + + total_supply = { + "supply": [{"amount": coin.amount, "denom": coin.denom} for coin in response.supply], + "pagination": { + "next": response.pagination.next_key, + "total": response.pagination.total + } + } + + return total_supply diff --git a/tests/client/chain/grpc/configurable_bank_query_servicer.py b/tests/client/chain/grpc/configurable_bank_query_servicer.py new file mode 100644 index 00000000..c9c27968 --- /dev/null +++ b/tests/client/chain/grpc/configurable_bank_query_servicer.py @@ -0,0 +1,28 @@ +from collections import deque + +from pyinjective.proto.cosmos.bank.v1beta1 import ( + query_pb2_grpc as bank_query_grpc, + query_pb2 as bank_query_pb, +) + + +class ConfigurableBankQueryServicer(bank_query_grpc.QueryServicer): + + def __init__(self): + super().__init__() + self.bank_params = deque() + self.balance_responses = deque() + self.balances_responses = deque() + self.total_supply_responses = deque() + + async def Params(self, request: bank_query_pb.QueryParamsRequest, context=None): + return self.bank_params.pop() + + async def Balance(self, request: bank_query_pb.QueryBalanceRequest, context=None): + return self.balance_responses.pop() + + async def AllBalances(self, request: bank_query_pb.QueryAllBalancesRequest, context=None): + return self.balances_responses.pop() + + async def TotalSupply(self, request: bank_query_pb.QueryTotalSupplyRequest, context=None): + return self.total_supply_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_auction_api.py b/tests/client/chain/grpc/test_chain_grpc_auction_api.py index 39912c0f..3e55b3cf 100644 --- a/tests/client/chain/grpc/test_chain_grpc_auction_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_auction_api.py @@ -91,6 +91,46 @@ async def test_fetch_module_state( assert (expected_state == module_state) + @pytest.mark.asyncio + async def test_fetch_module_state_when_no_highest_bid_present( + self, + auction_servicer, + ): + params = auction_pb.Params( + auction_period=604800, + min_next_bid_increment_rate="2500000000000000" + ) + state = genesis_pb.GenesisState( + params=params, + auction_round=50, + auction_ending_timestamp=1687504387, + ) + auction_servicer.module_states.append(auction_query_pb.QueryModuleStateResponse( + state=state + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel) + api._stub = auction_servicer + + module_state = await api.fetch_module_state() + expected_state = { + "params": { + "auction_period": 604800, + "min_next_bid_increment_rate": "2500000000000000", + }, + "auction_round": 50, + "highest_bid": { + "bidder": "", + "amount": "", + }, + "auction_ending_timestamp": 1687504387, + } + + assert (expected_state == module_state) + @pytest.mark.asyncio async def test_fetch_current_basket( self, diff --git a/tests/client/chain/grpc/test_chain_grpc_bank_api.py b/tests/client/chain/grpc/test_chain_grpc_bank_api.py new file mode 100644 index 00000000..b628916b --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_bank_api.py @@ -0,0 +1,179 @@ +import grpc +import pytest + +from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi +from pyinjective.constant import Network +from pyinjective.proto.cosmos.bank.v1beta1 import ( + bank_pb2 as bank_pb, + query_pb2_grpc as bank_query_grpc, + query_pb2 as bank_query_pb, +) +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer + + +@pytest.fixture +def bank_servicer(): + return ConfigurableBankQueryServicer() + + +class TestChainGrpcBankApi: + + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + bank_servicer, + ): + params = bank_pb.Params(default_send_enabled=True) + bank_servicer.bank_params.append(bank_query_pb.QueryParamsResponse( + params=params + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel) + api._stub = bank_servicer + + module_params = await api.fetch_module_params() + expected_params = { + "default_send_enabled": True, + } + + assert (expected_params == module_params) + + @pytest.mark.asyncio + async def test_fetch_balance( + self, + bank_servicer, + ): + balance = coin_pb.Coin( + denom="inj", + amount="988987297011197594664" + ) + bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse( + balance=balance + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel) + api._stub = bank_servicer + + bank_balance = await api.fetch_balance( + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", + denom="inj" + ) + expected_balance = { + "denom": "inj", + "amount": "988987297011197594664" + } + + assert (expected_balance == bank_balance) + + @pytest.mark.asyncio + async def test_fetch_balance( + self, + bank_servicer, + ): + balance = coin_pb.Coin( + denom="inj", + amount="988987297011197594664" + ) + bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse( + balance=balance + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel) + api._stub = bank_servicer + + bank_balance = await api.fetch_balance( + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", + denom="inj" + ) + expected_balance = { + "denom": "inj", + "amount": "988987297011197594664" + } + + assert (expected_balance == bank_balance) + + @pytest.mark.asyncio + async def test_fetch_balances( + self, + bank_servicer, + ): + first_balance = coin_pb.Coin( + denom="inj", + amount="988987297011197594664" + ) + second_balance = coin_pb.Coin( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + amount="54497408" + ) + pagination = pagination_pb.PageResponse(total=2) + + bank_servicer.balances_responses.append(bank_query_pb.QueryAllBalancesResponse( + balances=[first_balance, second_balance], + pagination=pagination, + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel) + api._stub = bank_servicer + + bank_balances = await api.fetch_balances( + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", + ) + expected_balances = { + "balances": [{"denom": coin.denom, "amount": coin.amount} for coin in [first_balance, second_balance]], + "pagination": {"total": 2}, + } + + assert (expected_balances == bank_balances) + + @pytest.mark.asyncio + async def test_fetch_total_supply( + self, + bank_servicer, + ): + first_supply = coin_pb.Coin( + denom="factory/inj108t3mlej0dph8er6ca2lq5cs9pdgzva5mqsn5p/position", + amount="5556700000000000000" + ) + second_supply = coin_pb.Coin( + denom="factory/inj10uycavvkc4uqr8tns3599r0t2xux4rz3y8apym/1684002313InjUsdt1d110C", + amount="1123456789111100000" + ) + pagination = pagination_pb.PageResponse( + next_key="factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja".encode(), + total=179 + ) + + bank_servicer.total_supply_responses.append(bank_query_pb.QueryTotalSupplyResponse( + supply=[first_supply, second_supply], + pagination=pagination, + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel) + api._stub = bank_servicer + + total_supply = await api.fetch_total_supply() + expected_supply = { + "supply": [{"denom": coin.denom, "amount": coin.amount} for coin in [first_supply, second_supply]], + "pagination": { + "next": "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja".encode(), + "total": 179}, + } + + assert (expected_supply == total_supply) From 011b9ee69c3c9a68f96e8a4175920c7f803a0761 Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 23 Jun 2023 00:27:19 -0300 Subject: [PATCH 03/27] (feat) Created the API component for the auth module. Created also the required classes to handle pagination options and results pagination information. --- pyinjective/async_client.py | 29 +--- .../client/chain/grpc/chain_grpc_auth_api.py | 44 +++++ pyinjective/client/chain/model/__init__.py | 0 pyinjective/client/chain/model/account.py | 42 +++++ pyinjective/client/chain/model/auth_params.py | 31 ++++ pyinjective/client/model/__init__.py | 0 pyinjective/client/model/pagination.py | 62 +++++++ .../grpc/configurable_auth_query_serciver.py | 24 +++ .../chain/grpc/test_chain_grpc_auth_api.py | 159 ++++++++++++++++++ .../chain/grpc/test_chain_grpc_bank_api.py | 1 - 10 files changed, 370 insertions(+), 22 deletions(-) create mode 100644 pyinjective/client/chain/grpc/chain_grpc_auth_api.py create mode 100644 pyinjective/client/chain/model/__init__.py create mode 100644 pyinjective/client/chain/model/account.py create mode 100644 pyinjective/client/chain/model/auth_params.py create mode 100644 pyinjective/client/model/__init__.py create mode 100644 pyinjective/client/model/pagination.py create mode 100644 tests/client/chain/grpc/configurable_auth_query_serciver.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_auth_api.py diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 562914b2..2522a7f5 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -12,6 +12,7 @@ from pyinjective.composer import Composer from . import constant +from .client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi from .client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi from .core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket from .core.token import Token @@ -23,12 +24,6 @@ query_pb2_grpc as tendermint_query_grpc, query_pb2 as tendermint_query, ) - -from .proto.cosmos.auth.v1beta1 import ( - query_pb2_grpc as auth_query_grpc, - query_pb2 as auth_query, - auth_pb2 as auth_type, -) from .proto.cosmos.authz.v1beta1 import ( query_pb2_grpc as authz_query_grpc, query_pb2 as authz_query, @@ -39,10 +34,6 @@ query_pb2 as authz_query, authz_pb2 as authz_type, ) -from .proto.cosmos.bank.v1beta1 import ( - query_pb2_grpc as bank_query_grpc, - query_pb2 as bank_query, -) from .proto.cosmos.tx.v1beta1 import ( service_pb2_grpc as tx_service_grpc, service_pb2 as tx_service, @@ -122,9 +113,7 @@ def __init__( self.stubCosmosTendermint = tendermint_query_grpc.ServiceStub( self.chain_channel ) - self.stubAuth = auth_query_grpc.QueryStub(self.chain_channel) self.stubAuthz = authz_query_grpc.QueryStub(self.chain_channel) - self.bank_api = ChainGrpcBankApi(channel=self.chain_channel) self.stubTx = tx_service_grpc.ServiceStub(self.chain_channel) # attempt to load from disk @@ -193,6 +182,9 @@ def __init__( self._derivative_markets: Optional[Dict[str, DerivativeMarket]] = None self._binary_option_markets: Optional[Dict[str, BinaryOptionMarket]] = None + self.bank_api = ChainGrpcBankApi(channel=self.chain_channel) + self.auth_api = ChainGrpcAuthApi(channel=self.chain_channel) + async def all_tokens(self) -> Dict[str, Token]: if self._tokens is None: async with self._tokens_and_markets_initialization_lock: @@ -342,15 +334,10 @@ async def get_latest_block(self) -> tendermint_query.GetLatestBlockResponse: async def get_account(self, address: str) -> Optional[account_pb2.EthAccount]: try: - metadata = await self.load_cookie(type="chain") - account_any = (await self.stubAuth.Account( - auth_query.QueryAccountRequest.__call__(address=address), metadata=metadata - )).account - account = account_pb2.EthAccount() - if account_any.Is(account.DESCRIPTOR): - account_any.Unpack(account) - self.number = int(account.base_account.account_number) - self.sequence = int(account.base_account.sequence) + await self.load_cookie(type="chain") + account = await self.auth_api.fetch_account(address=address) + self.number = account.account_number + self.sequence = account.sequence except Exception as e: LoggerProvider().logger_for_class(logging_class=self.__class__).debug( f"error while fetching sequence and number {e}") diff --git a/pyinjective/client/chain/grpc/chain_grpc_auth_api.py b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py new file mode 100644 index 00000000..c9265029 --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py @@ -0,0 +1,44 @@ +from typing import List, Tuple + +from grpc.aio import Channel + +from pyinjective.client.chain.model.account import Account +from pyinjective.client.chain.model.auth_params import AuthParams +from pyinjective.client.model.pagination import PaginationOption + +from pyinjective.client.model.pagination import Pagination +from pyinjective.proto.cosmos.auth.v1beta1 import ( + query_pb2_grpc as auth_query_grpc, + query_pb2 as auth_query_pb, +) + + +class ChainGrpcAuthApi: + + def __init__(self, channel: Channel): + self._stub = auth_query_grpc.QueryStub(channel) + + async def fetch_module_params(self) -> AuthParams: + request = auth_query_pb.QueryParamsRequest() + response = await self._stub.Params(request) + + module_params = AuthParams.from_proto_response(response=response) + + return module_params + + async def fetch_account(self, address: str) -> Account: + request = auth_query_pb.QueryAccountRequest(address=address) + response = await self._stub.Account(request) + + account = Account.from_proto(proto_account=response.account) + + return account + + async def fetch_accounts(self, pagination_option: PaginationOption) -> Tuple[List[Account], PaginationOption]: + request = auth_query_pb.QueryAccountsRequest(pagination=pagination_option.create_pagination_request()) + response = await self._stub.Accounts(request) + + accounts = [Account.from_proto(proto_account=proto_account) for proto_account in response.accounts] + response_pagination = Pagination.from_proto(proto_pagination=response.pagination) + + return accounts, response_pagination \ No newline at end of file diff --git a/pyinjective/client/chain/model/__init__.py b/pyinjective/client/chain/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/model/account.py b/pyinjective/client/chain/model/account.py new file mode 100644 index 00000000..da497464 --- /dev/null +++ b/pyinjective/client/chain/model/account.py @@ -0,0 +1,42 @@ +from google.protobuf import any_pb2 + +from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb + +class Account: + + def __init__( + self, + address: str, + pub_key_type_url: str, + pub_key_value: bytes, + account_number: int, + sequence: int, + code_hash: str, + ): + super().__init__() + self.address = address + self.pub_key_type_url = pub_key_type_url + self.pub_key_value = pub_key_value + self.account_number = account_number + self.sequence = sequence + self.code_hash = code_hash + + @classmethod + def from_proto(cls, proto_account: any_pb2.Any): + eth_account = account_pb.EthAccount() + proto_account.Unpack(eth_account) + pub_key_type_url = None + pub_key_value = None + + if eth_account.base_account.pub_key is not None: + pub_key_type_url = eth_account.base_account.pub_key.type_url + pub_key_value = eth_account.base_account.pub_key.value + + return cls( + address=eth_account.base_account.address, + pub_key_type_url=pub_key_type_url, + pub_key_value=pub_key_value, + account_number=eth_account.base_account.account_number, + sequence=eth_account.base_account.sequence, + code_hash=f"0x{eth_account.code_hash.hex()}", + ) diff --git a/pyinjective/client/chain/model/auth_params.py b/pyinjective/client/chain/model/auth_params.py new file mode 100644 index 00000000..a19d92c6 --- /dev/null +++ b/pyinjective/client/chain/model/auth_params.py @@ -0,0 +1,31 @@ +from pyinjective.proto.cosmos.auth.v1beta1 import ( + query_pb2_grpc as auth_query_grpc, + query_pb2 as auth_query_pb, +) + +class AuthParams: + + def __init__( + self, + max_memo_characters: int, + tx_sig_limit: int, + tx_size_cost_per_byte: int, + sig_verify_cost_ed25519: int, + sig_verify_cost_secp256k1: int, + ): + super().__init__() + self.max_memo_characters = max_memo_characters + self.tx_sig_limit = tx_sig_limit + self.tx_size_cost_per_byte = tx_size_cost_per_byte + self.sig_verify_cost_ed25519 = sig_verify_cost_ed25519 + self.sig_verify_cost_secp256k1 = sig_verify_cost_secp256k1 + + @classmethod + def from_proto_response(cls, response: auth_query_pb.QueryParamsResponse): + return cls( + max_memo_characters=response.params.max_memo_characters, + tx_sig_limit=response.params.tx_sig_limit, + tx_size_cost_per_byte=response.params.tx_size_cost_per_byte, + sig_verify_cost_ed25519=response.params.sig_verify_cost_ed25519, + sig_verify_cost_secp256k1=response.params.sig_verify_cost_secp256k1 + ) \ No newline at end of file diff --git a/pyinjective/client/model/__init__.py b/pyinjective/client/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/model/pagination.py b/pyinjective/client/model/pagination.py new file mode 100644 index 00000000..c425b173 --- /dev/null +++ b/pyinjective/client/model/pagination.py @@ -0,0 +1,62 @@ +from typing import Optional + +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb + + +class PaginationOption: + + def __init__( + self, + key: Optional[str], + offset: Optional[int], + limit: Optional[int], + reverse: Optional[bool], + count_total: Optional[bool], + ): + super().__init__() + self.key = key + self.offset = offset + self.limit = limit + self.reverse = reverse + self.count_total = count_total + + def create_pagination_request(self) -> pagination_pb.PageRequest: + page_request = pagination_pb.PageRequest() + + if self.key is not None: + page_request.key = bytes.fromhex(self.key) + if self.offset is not None: + page_request.offset = self.offset + if self.limit is not None: + page_request.limit = self.limit + if self.reverse is not None: + page_request.reverse = self.reverse + if self.count_total is not None: + page_request.count_total = self.count_total + + return page_request + + +class Pagination: + + def __init__( + self, + next: Optional[str] = None, + total: Optional[int] = None, + ): + super().__init__() + self.next = next + self.total = total + + @classmethod + def from_proto(cls, proto_pagination: pagination_pb.PageResponse): + next = None + + if proto_pagination.next_key is not None: + next = f"0x{proto_pagination.next_key.hex()}" + total = proto_pagination.total + + return cls( + next=next, + total=total, + ) \ No newline at end of file diff --git a/tests/client/chain/grpc/configurable_auth_query_serciver.py b/tests/client/chain/grpc/configurable_auth_query_serciver.py new file mode 100644 index 00000000..7e856af9 --- /dev/null +++ b/tests/client/chain/grpc/configurable_auth_query_serciver.py @@ -0,0 +1,24 @@ +from collections import deque + +from pyinjective.proto.cosmos.auth.v1beta1 import ( + query_pb2_grpc as auth_query_grpc, + query_pb2 as auth_query_pb, +) + + +class ConfigurableAuthQueryServicer(auth_query_grpc.QueryServicer): + + def __init__(self): + super().__init__() + self.auth_params = deque() + self.account_responses = deque() + self.accounts_responses = deque() + + async def Params(self, request: auth_query_pb.QueryParamsRequest, context=None): + return self.auth_params.pop() + + async def Account(self, request: auth_query_pb.QueryAccountRequest, context=None): + return self.account_responses.pop() + + async def Accounts(self, request: auth_query_pb.QueryAccountsRequest, context=None): + return self.accounts_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_auth_api.py b/tests/client/chain/grpc/test_chain_grpc_auth_api.py new file mode 100644 index 00000000..71436e59 --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_auth_api.py @@ -0,0 +1,159 @@ +import grpc +import pytest +from google.protobuf import any_pb2 + +from pyinjective.client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.constant import Network +from pyinjective.proto.cosmos.auth.v1beta1 import ( + auth_pb2 as auth_pb, + query_pb2 as auth_query_pb, +) +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from pyinjective.proto.injective.crypto.v1beta1.ethsecp256k1 import keys_pb2 as keys_pb +from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb +from tests.client.chain.grpc.configurable_auth_query_serciver import ConfigurableAuthQueryServicer + +@pytest.fixture +def auth_servicer(): + return ConfigurableAuthQueryServicer() + + +class TestChainGrpcAuthApi: + + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + auth_servicer, + ): + params = auth_pb.Params( + max_memo_characters=256, + tx_sig_limit=7, + tx_size_cost_per_byte=10, + sig_verify_cost_ed25519=590, + sig_verify_cost_secp256k1=1000, + ) + auth_servicer.auth_params.append(auth_query_pb.QueryParamsResponse( + params=params + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthApi(channel=channel) + api._stub = auth_servicer + + module_params = await api.fetch_module_params() + + assert (params.max_memo_characters == module_params.max_memo_characters) + assert (params.tx_sig_limit == module_params.tx_sig_limit) + assert (params.tx_size_cost_per_byte == module_params.tx_size_cost_per_byte) + assert (params.sig_verify_cost_ed25519 == module_params.sig_verify_cost_ed25519) + assert (params.sig_verify_cost_secp256k1 == module_params.sig_verify_cost_secp256k1) + + @pytest.mark.asyncio + async def test_fetch_account( + self, + auth_servicer, + ): + pub_key = keys_pb.PubKey( + key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375" + ) + any_pub_key = any_pb2.Any() + any_pub_key.Pack(pub_key, type_url_prefix="") + + base_account = auth_pb.BaseAccount( + address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr", + pub_key=any_pub_key, + account_number=39, + sequence=697457, + ) + account = account_pb.EthAccount( + base_account=base_account, + code_hash=b"\305\322F\001\206\367#<\222~}\262\334\307\003\300\345\000\266S\312\202\';{\372\330\004]\205\244p" + ) + + any_account = any_pb2.Any() + any_account.Pack(account, type_url_prefix="") + auth_servicer.account_responses.append(auth_query_pb.QueryAccountResponse( + account=any_account + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthApi(channel=channel) + api._stub = auth_servicer + + response_account = await api.fetch_account(address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr") + + assert (f"0x{account.code_hash.hex()}" == response_account.code_hash) + assert (base_account.address == response_account.address) + assert (any_pub_key.type_url == response_account.pub_key_type_url) + assert (any_pub_key.value == response_account.pub_key_value) + assert (base_account.account_number == response_account.account_number) + assert (base_account.sequence == response_account.sequence) + + @pytest.mark.asyncio + async def test_fetch_accounts( + self, + auth_servicer, + ): + pub_key = keys_pb.PubKey( + key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375" + ) + any_pub_key = any_pb2.Any() + any_pub_key.Pack(pub_key, type_url_prefix="") + + base_account = auth_pb.BaseAccount( + address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr", + pub_key=any_pub_key, + account_number=39, + sequence=697457, + ) + account = account_pb.EthAccount( + base_account=base_account, + code_hash=b"\305\322F\001\206\367#<\222~}\262\334\307\003\300\345\000\266S\312\202\';{\372\330\004]\205\244p" + ) + + result_pagination = pagination_pb.PageResponse( + next_key=b"\001\032\264\007Z\224$]\377s8\343\004-\265\267\314?B\341", + total=16036, + ) + + any_account = any_pb2.Any() + any_account.Pack(account, type_url_prefix="") + auth_servicer.accounts_responses.append(auth_query_pb.QueryAccountsResponse( + accounts=[any_account], + pagination=result_pagination, + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthApi(channel=channel) + api._stub = auth_servicer + + pagination_option = PaginationOption( + key="011ab4075a94245dff7338e3042db5b7cc3f42e1", + offset=10, + limit=30, + reverse=False, + count_total=True, + ) + + response_accounts, response_pagination = await api.fetch_accounts(pagination_option=pagination_option) + + assert (1 == len(response_accounts)) + + response_account = response_accounts[0] + + assert (f"0x{account.code_hash.hex()}" == response_account.code_hash) + assert (base_account.address == response_account.address) + assert (any_pub_key.type_url == response_account.pub_key_type_url) + assert (any_pub_key.value == response_account.pub_key_value) + assert (base_account.account_number == response_account.account_number) + assert (base_account.sequence == response_account.sequence) + + assert (f"0x{result_pagination.next_key.hex()}" == response_pagination.next) + assert (result_pagination.total == response_pagination.total) \ No newline at end of file diff --git a/tests/client/chain/grpc/test_chain_grpc_bank_api.py b/tests/client/chain/grpc/test_chain_grpc_bank_api.py index b628916b..3e27e69c 100644 --- a/tests/client/chain/grpc/test_chain_grpc_bank_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_bank_api.py @@ -5,7 +5,6 @@ from pyinjective.constant import Network from pyinjective.proto.cosmos.bank.v1beta1 import ( bank_pb2 as bank_pb, - query_pb2_grpc as bank_query_grpc, query_pb2 as bank_query_pb, ) from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb From a5040cddc8602c8a44ebc3b6ff408b1732346cf0 Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 21 Jun 2023 12:46:25 -0300 Subject: [PATCH 04/27] (feat) Created the API component for the auction module (with the unit tests) --- pyinjective/client/__init__.py | 0 pyinjective/client/chain/__init__.py | 0 pyinjective/client/chain/grpc/__init__.py | 0 .../chain/grpc/chain_grpc_auction_api.py | 66 +++++++++ tests/client/__init__.py | 0 tests/client/chain/__init__.py | 0 tests/client/chain/grpc/__init__.py | 0 .../configurable_auction_query_servicer.py | 24 ++++ .../chain/grpc/test_chain_grpc_auction_api.py | 131 ++++++++++++++++++ 9 files changed, 221 insertions(+) create mode 100644 pyinjective/client/__init__.py create mode 100644 pyinjective/client/chain/__init__.py create mode 100644 pyinjective/client/chain/grpc/__init__.py create mode 100644 pyinjective/client/chain/grpc/chain_grpc_auction_api.py create mode 100644 tests/client/__init__.py create mode 100644 tests/client/chain/__init__.py create mode 100644 tests/client/chain/grpc/__init__.py create mode 100644 tests/client/chain/grpc/configurable_auction_query_servicer.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_auction_api.py diff --git a/pyinjective/client/__init__.py b/pyinjective/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/__init__.py b/pyinjective/client/chain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/grpc/__init__.py b/pyinjective/client/chain/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/grpc/chain_grpc_auction_api.py b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py new file mode 100644 index 00000000..1e54cc27 --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py @@ -0,0 +1,66 @@ +from typing import Dict, Any + +from grpc.aio import Channel + +from pyinjective.proto.injective.auction.v1beta1 import ( + query_pb2_grpc as auction_query_grpc, + query_pb2 as auction_query_pb, +) + + +class ChainGrpcAuctionApi: + + def __init__(self, channel: Channel): + self._stub = auction_query_grpc.QueryStub(channel) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = auction_query_pb.QueryAuctionParamsRequest() + response = await self._stub.AuctionParams(request) + + module_params = { + "auction_period": response.params.auction_period, + "min_next_bid_increment_rate": response.params.min_next_bid_increment_rate, + } + + return module_params + + async def fetch_module_state(self) -> Dict[str, Any]: + request = auction_query_pb.QueryModuleStateRequest() + response = await self._stub.AuctionModuleState(request) + + if response.state.highest_bid is None: + highest_bid = { + "bidder": "", + "amount": "", + } + else: + highest_bid = { + "bidder": response.state.highest_bid.bidder, + "amount": response.state.highest_bid.amount, + } + + module_state = { + "params": { + "auction_period": response.state.params.auction_period, + "min_next_bid_increment_rate": response.state.params.min_next_bid_increment_rate, + }, + "auction_round": response.state.auction_round, + "highest_bid": highest_bid, + "auction_ending_timestamp": response.state.auction_ending_timestamp, + } + + return module_state + + async def fetch_current_basket(self) -> Dict[str, Any]: + request = auction_query_pb.QueryCurrentAuctionBasketRequest() + response = await self._stub.CurrentAuctionBasket(request) + + current_basket = { + "amount_list": [{"amount": coin.amount, "denom": coin.denom} for coin in response.amount], + "auction_round": response.auctionRound, + "auction_closing_time": response.auctionClosingTime, + "highest_bidder": response.highestBidder, + "highest_bid_amount": response.highestBidAmount, + } + + return current_basket diff --git a/tests/client/__init__.py b/tests/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/__init__.py b/tests/client/chain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/grpc/__init__.py b/tests/client/chain/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/grpc/configurable_auction_query_servicer.py b/tests/client/chain/grpc/configurable_auction_query_servicer.py new file mode 100644 index 00000000..aa7ba51a --- /dev/null +++ b/tests/client/chain/grpc/configurable_auction_query_servicer.py @@ -0,0 +1,24 @@ +from collections import deque + +from pyinjective.proto.injective.auction.v1beta1 import ( + query_pb2_grpc as auction_query_grpc, + query_pb2 as auction_query_pb, +) + + +class ConfigurableAuctionQueryServicer(auction_query_grpc.QueryServicer): + + def __init__(self): + super().__init__() + self.auction_params = deque() + self.module_states = deque() + self.current_baskets = deque() + + async def AuctionParams(self, request: auction_query_pb.QueryAuctionParamsRequest, context=None): + return self.auction_params.pop() + + async def AuctionModuleState(self, request: auction_query_pb.QueryModuleStateRequest, context=None): + return self.module_states.pop() + + async def CurrentAuctionBasket(self, request: auction_query_pb.QueryCurrentAuctionBasketRequest, context=None): + return self.current_baskets.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_auction_api.py b/tests/client/chain/grpc/test_chain_grpc_auction_api.py new file mode 100644 index 00000000..39912c0f --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_auction_api.py @@ -0,0 +1,131 @@ +import grpc +import pytest + +from pyinjective.client.chain.grpc.chain_grpc_auction_api import ChainGrpcAuctionApi +from pyinjective.constant import Network +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.injective.auction.v1beta1 import ( + auction_pb2 as auction_pb, + genesis_pb2 as genesis_pb, + query_pb2 as auction_query_pb, +) +from tests.client.chain.grpc.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer + + +@pytest.fixture +def auction_servicer(): + return ConfigurableAuctionQueryServicer() + + +class TestChainGrpcAuctionApi: + + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + auction_servicer, + ): + params = auction_pb.Params( + auction_period=604800, + min_next_bid_increment_rate="2500000000000000" + ) + auction_servicer.auction_params.append(auction_query_pb.QueryAuctionParamsResponse( + params=params + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel) + api._stub = auction_servicer + + module_params = await api.fetch_module_params() + expected_params = { + "auction_period": 604800, + "min_next_bid_increment_rate": "2500000000000000", + } + + assert (expected_params == module_params) + + @pytest.mark.asyncio + async def test_fetch_module_state( + self, + auction_servicer, + ): + params = auction_pb.Params( + auction_period=604800, + min_next_bid_increment_rate="2500000000000000" + ) + highest_bid = auction_pb.Bid( + bidder="inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + amount="\n\003inj\022\0232347518723906280000", + ) + state = genesis_pb.GenesisState( + params=params, + auction_round=50, + highest_bid=highest_bid, + auction_ending_timestamp=1687504387, + ) + auction_servicer.module_states.append(auction_query_pb.QueryModuleStateResponse( + state=state + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel) + api._stub = auction_servicer + + module_state = await api.fetch_module_state() + expected_state = { + "params": { + "auction_period": 604800, + "min_next_bid_increment_rate": "2500000000000000", + }, + "auction_round": 50, + "highest_bid": { + "bidder": "inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + "amount": "\n\003inj\022\0232347518723906280000", + }, + "auction_ending_timestamp": 1687504387, + } + + assert (expected_state == module_state) + + @pytest.mark.asyncio + async def test_fetch_current_basket( + self, + auction_servicer, + ): + first_amount = coin_pb.Coin( + amount="15059786755", + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + ) + second_amount = coin_pb.Coin( + amount="200000", + denom="peggy0xf9152067989BDc8783fF586624124C05A529A5D1", + ) + + auction_servicer.current_baskets.append(auction_query_pb.QueryCurrentAuctionBasketResponse( + amount=[first_amount, second_amount], + auctionRound=50, + auctionClosingTime=1687504387, + highestBidder="inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + highestBidAmount="2347518723906280000", + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel) + api._stub = auction_servicer + + current_basket = await api.fetch_current_basket() + expected_basket = { + "amount_list": [{"amount": coin.amount, "denom": coin.denom} for coin in [first_amount, second_amount]], + "auction_round": 50, + "auction_closing_time": 1687504387, + "highest_bidder": "inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + "highest_bid_amount": "2347518723906280000", + } + + assert (expected_basket == current_basket) From df4436d098c29caf965e16b7c9b5b5b92bfd3e5c Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 21 Jun 2023 23:58:22 -0300 Subject: [PATCH 05/27] (feat) Created the API class for the bank module (with its unit tests) --- pyinjective/async_client.py | 7 +- .../client/chain/grpc/chain_grpc_bank_api.py | 58 ++++++ .../grpc/configurable_bank_query_servicer.py | 28 +++ .../chain/grpc/test_chain_grpc_auction_api.py | 40 ++++ .../chain/grpc/test_chain_grpc_bank_api.py | 179 ++++++++++++++++++ 5 files changed, 309 insertions(+), 3 deletions(-) create mode 100644 pyinjective/client/chain/grpc/chain_grpc_bank_api.py create mode 100644 tests/client/chain/grpc/configurable_bank_query_servicer.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_bank_api.py diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index f279b45f..7b0b7a59 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -10,6 +10,7 @@ from pyinjective.composer import Composer from . import constant +from .client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi from .core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket from .core.network import Network from .core.token import Token @@ -77,7 +78,7 @@ def __init__( self.stubCosmosTendermint = tendermint_query_grpc.ServiceStub(self.chain_channel) self.stubAuth = auth_query_grpc.QueryStub(self.chain_channel) self.stubAuthz = authz_query_grpc.QueryStub(self.chain_channel) - self.stubBank = bank_query_grpc.QueryStub(self.chain_channel) + self.bank_api = ChainGrpcBankApi(channel=self.chain_channel) self.stubTx = tx_service_grpc.ServiceStub(self.chain_channel) self.exchange_cookie = "" @@ -256,10 +257,10 @@ async def get_grants(self, granter: str, grantee: str, **kwargs): ) async def get_bank_balances(self, address: str): - return await self.stubBank.AllBalances(bank_query.QueryAllBalancesRequest(address=address)) + return await self.bank_api.fetch_balances(account_address=address) async def get_bank_balance(self, address: str, denom: str): - return await self.stubBank.Balance(bank_query.QueryBalanceRequest(address=address, denom=denom)) + return await self.bank_api.fetch_balance(account_address=address, denom=denom) # Injective Exchange client methods diff --git a/pyinjective/client/chain/grpc/chain_grpc_bank_api.py b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py new file mode 100644 index 00000000..7e916fba --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py @@ -0,0 +1,58 @@ +from typing import Dict, Any + +from grpc.aio import Channel + +from pyinjective.proto.cosmos.bank.v1beta1 import ( + query_pb2_grpc as bank_query_grpc, + query_pb2 as bank_query_pb, +) + +class ChainGrpcBankApi: + + def __init__(self, channel: Channel): + self._stub = bank_query_grpc.QueryStub(channel) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = bank_query_pb.QueryParamsRequest() + response = await self._stub.Params(request) + + module_params = { + "default_send_enabled": response.params.default_send_enabled, + } + + return module_params + + async def fetch_balance(self, account_address: str, denom: str) -> Dict[str, Any]: + request = bank_query_pb.QueryBalanceRequest(address=account_address, denom=denom) + response = await self._stub.Balance(request) + + bank_balance = { + "amount": response.balance.amount, + "denom": response.balance.denom, + } + + return bank_balance + + async def fetch_balances(self, account_address: str) -> Dict[str, Any]: + request = bank_query_pb.QueryAllBalancesRequest(address=account_address) + response = await self._stub.AllBalances(request) + + bank_balances = { + "balances": [{"amount": coin.amount, "denom": coin.denom} for coin in response.balances], + "pagination": {"total": response.pagination.total}} + + return bank_balances + + async def fetch_total_supply(self) -> Dict[str, Any]: + request = bank_query_pb.QueryTotalSupplyRequest() + response = await self._stub.TotalSupply(request) + + total_supply = { + "supply": [{"amount": coin.amount, "denom": coin.denom} for coin in response.supply], + "pagination": { + "next": response.pagination.next_key, + "total": response.pagination.total + } + } + + return total_supply diff --git a/tests/client/chain/grpc/configurable_bank_query_servicer.py b/tests/client/chain/grpc/configurable_bank_query_servicer.py new file mode 100644 index 00000000..c9c27968 --- /dev/null +++ b/tests/client/chain/grpc/configurable_bank_query_servicer.py @@ -0,0 +1,28 @@ +from collections import deque + +from pyinjective.proto.cosmos.bank.v1beta1 import ( + query_pb2_grpc as bank_query_grpc, + query_pb2 as bank_query_pb, +) + + +class ConfigurableBankQueryServicer(bank_query_grpc.QueryServicer): + + def __init__(self): + super().__init__() + self.bank_params = deque() + self.balance_responses = deque() + self.balances_responses = deque() + self.total_supply_responses = deque() + + async def Params(self, request: bank_query_pb.QueryParamsRequest, context=None): + return self.bank_params.pop() + + async def Balance(self, request: bank_query_pb.QueryBalanceRequest, context=None): + return self.balance_responses.pop() + + async def AllBalances(self, request: bank_query_pb.QueryAllBalancesRequest, context=None): + return self.balances_responses.pop() + + async def TotalSupply(self, request: bank_query_pb.QueryTotalSupplyRequest, context=None): + return self.total_supply_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_auction_api.py b/tests/client/chain/grpc/test_chain_grpc_auction_api.py index 39912c0f..3e55b3cf 100644 --- a/tests/client/chain/grpc/test_chain_grpc_auction_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_auction_api.py @@ -91,6 +91,46 @@ async def test_fetch_module_state( assert (expected_state == module_state) + @pytest.mark.asyncio + async def test_fetch_module_state_when_no_highest_bid_present( + self, + auction_servicer, + ): + params = auction_pb.Params( + auction_period=604800, + min_next_bid_increment_rate="2500000000000000" + ) + state = genesis_pb.GenesisState( + params=params, + auction_round=50, + auction_ending_timestamp=1687504387, + ) + auction_servicer.module_states.append(auction_query_pb.QueryModuleStateResponse( + state=state + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel) + api._stub = auction_servicer + + module_state = await api.fetch_module_state() + expected_state = { + "params": { + "auction_period": 604800, + "min_next_bid_increment_rate": "2500000000000000", + }, + "auction_round": 50, + "highest_bid": { + "bidder": "", + "amount": "", + }, + "auction_ending_timestamp": 1687504387, + } + + assert (expected_state == module_state) + @pytest.mark.asyncio async def test_fetch_current_basket( self, diff --git a/tests/client/chain/grpc/test_chain_grpc_bank_api.py b/tests/client/chain/grpc/test_chain_grpc_bank_api.py new file mode 100644 index 00000000..b628916b --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_bank_api.py @@ -0,0 +1,179 @@ +import grpc +import pytest + +from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi +from pyinjective.constant import Network +from pyinjective.proto.cosmos.bank.v1beta1 import ( + bank_pb2 as bank_pb, + query_pb2_grpc as bank_query_grpc, + query_pb2 as bank_query_pb, +) +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer + + +@pytest.fixture +def bank_servicer(): + return ConfigurableBankQueryServicer() + + +class TestChainGrpcBankApi: + + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + bank_servicer, + ): + params = bank_pb.Params(default_send_enabled=True) + bank_servicer.bank_params.append(bank_query_pb.QueryParamsResponse( + params=params + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel) + api._stub = bank_servicer + + module_params = await api.fetch_module_params() + expected_params = { + "default_send_enabled": True, + } + + assert (expected_params == module_params) + + @pytest.mark.asyncio + async def test_fetch_balance( + self, + bank_servicer, + ): + balance = coin_pb.Coin( + denom="inj", + amount="988987297011197594664" + ) + bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse( + balance=balance + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel) + api._stub = bank_servicer + + bank_balance = await api.fetch_balance( + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", + denom="inj" + ) + expected_balance = { + "denom": "inj", + "amount": "988987297011197594664" + } + + assert (expected_balance == bank_balance) + + @pytest.mark.asyncio + async def test_fetch_balance( + self, + bank_servicer, + ): + balance = coin_pb.Coin( + denom="inj", + amount="988987297011197594664" + ) + bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse( + balance=balance + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel) + api._stub = bank_servicer + + bank_balance = await api.fetch_balance( + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", + denom="inj" + ) + expected_balance = { + "denom": "inj", + "amount": "988987297011197594664" + } + + assert (expected_balance == bank_balance) + + @pytest.mark.asyncio + async def test_fetch_balances( + self, + bank_servicer, + ): + first_balance = coin_pb.Coin( + denom="inj", + amount="988987297011197594664" + ) + second_balance = coin_pb.Coin( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + amount="54497408" + ) + pagination = pagination_pb.PageResponse(total=2) + + bank_servicer.balances_responses.append(bank_query_pb.QueryAllBalancesResponse( + balances=[first_balance, second_balance], + pagination=pagination, + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel) + api._stub = bank_servicer + + bank_balances = await api.fetch_balances( + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", + ) + expected_balances = { + "balances": [{"denom": coin.denom, "amount": coin.amount} for coin in [first_balance, second_balance]], + "pagination": {"total": 2}, + } + + assert (expected_balances == bank_balances) + + @pytest.mark.asyncio + async def test_fetch_total_supply( + self, + bank_servicer, + ): + first_supply = coin_pb.Coin( + denom="factory/inj108t3mlej0dph8er6ca2lq5cs9pdgzva5mqsn5p/position", + amount="5556700000000000000" + ) + second_supply = coin_pb.Coin( + denom="factory/inj10uycavvkc4uqr8tns3599r0t2xux4rz3y8apym/1684002313InjUsdt1d110C", + amount="1123456789111100000" + ) + pagination = pagination_pb.PageResponse( + next_key="factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja".encode(), + total=179 + ) + + bank_servicer.total_supply_responses.append(bank_query_pb.QueryTotalSupplyResponse( + supply=[first_supply, second_supply], + pagination=pagination, + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel) + api._stub = bank_servicer + + total_supply = await api.fetch_total_supply() + expected_supply = { + "supply": [{"denom": coin.denom, "amount": coin.amount} for coin in [first_supply, second_supply]], + "pagination": { + "next": "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja".encode(), + "total": 179}, + } + + assert (expected_supply == total_supply) From 42c3cb4949b0a6b4a622a6a8ca61dfad93707bdd Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 23 Jun 2023 00:27:19 -0300 Subject: [PATCH 06/27] (feat) Created the API component for the auth module. Created also the required classes to handle pagination options and results pagination information. --- pyinjective/async_client.py | 21 +-- .../client/chain/grpc/chain_grpc_auth_api.py | 44 +++++ pyinjective/client/chain/model/__init__.py | 0 pyinjective/client/chain/model/account.py | 42 +++++ pyinjective/client/chain/model/auth_params.py | 31 ++++ pyinjective/client/model/__init__.py | 0 pyinjective/client/model/pagination.py | 62 +++++++ .../grpc/configurable_auth_query_serciver.py | 24 +++ .../chain/grpc/test_chain_grpc_auth_api.py | 159 ++++++++++++++++++ .../chain/grpc/test_chain_grpc_bank_api.py | 1 - 10 files changed, 369 insertions(+), 15 deletions(-) create mode 100644 pyinjective/client/chain/grpc/chain_grpc_auth_api.py create mode 100644 pyinjective/client/chain/model/__init__.py create mode 100644 pyinjective/client/chain/model/account.py create mode 100644 pyinjective/client/chain/model/auth_params.py create mode 100644 pyinjective/client/model/__init__.py create mode 100644 pyinjective/client/model/pagination.py create mode 100644 tests/client/chain/grpc/configurable_auth_query_serciver.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_auth_api.py diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 7b0b7a59..1aa86805 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -10,17 +10,14 @@ from pyinjective.composer import Composer from . import constant +from .client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi from .client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi from .core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket from .core.network import Network from .core.token import Token from .exceptions import NotFoundError -from .proto.cosmos.auth.v1beta1 import query_pb2 as auth_query -from .proto.cosmos.auth.v1beta1 import query_pb2_grpc as auth_query_grpc from .proto.cosmos.authz.v1beta1 import query_pb2 as authz_query from .proto.cosmos.authz.v1beta1 import query_pb2_grpc as authz_query_grpc -from .proto.cosmos.bank.v1beta1 import query_pb2 as bank_query -from .proto.cosmos.bank.v1beta1 import query_pb2_grpc as bank_query_grpc from .proto.cosmos.base.abci.v1beta1 import abci_pb2 as abci_type from .proto.cosmos.base.tendermint.v1beta1 import query_pb2 as tendermint_query from .proto.cosmos.base.tendermint.v1beta1 import query_pb2_grpc as tendermint_query_grpc @@ -76,9 +73,7 @@ def __init__( ) self.stubCosmosTendermint = tendermint_query_grpc.ServiceStub(self.chain_channel) - self.stubAuth = auth_query_grpc.QueryStub(self.chain_channel) self.stubAuthz = authz_query_grpc.QueryStub(self.chain_channel) - self.bank_api = ChainGrpcBankApi(channel=self.chain_channel) self.stubTx = tx_service_grpc.ServiceStub(self.chain_channel) self.exchange_cookie = "" @@ -123,6 +118,9 @@ def __init__( self._derivative_markets: Optional[Dict[str, DerivativeMarket]] = None self._binary_option_markets: Optional[Dict[str, BinaryOptionMarket]] = None + self.bank_api = ChainGrpcBankApi(channel=self.chain_channel) + self.auth_api = ChainGrpcAuthApi(channel=self.chain_channel) + async def all_tokens(self) -> Dict[str, Token]: if self._tokens is None: async with self._tokens_and_markets_initialization_lock: @@ -188,14 +186,9 @@ async def get_latest_block(self) -> tendermint_query.GetLatestBlockResponse: async def get_account(self, address: str) -> Optional[account_pb2.EthAccount]: try: metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) - account_any = ( - await self.stubAuth.Account(auth_query.QueryAccountRequest(address=address), metadata=metadata) - ).account - account = account_pb2.EthAccount() - if account_any.Is(account.DESCRIPTOR): - account_any.Unpack(account) - self.number = int(account.base_account.account_number) - self.sequence = int(account.base_account.sequence) + account = await self.auth_api.fetch_account(address=address) + self.number = account.account_number + self.sequence = account.sequence except Exception as e: LoggerProvider().logger_for_class(logging_class=self.__class__).debug( f"error while fetching sequence and number {e}" diff --git a/pyinjective/client/chain/grpc/chain_grpc_auth_api.py b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py new file mode 100644 index 00000000..c9265029 --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py @@ -0,0 +1,44 @@ +from typing import List, Tuple + +from grpc.aio import Channel + +from pyinjective.client.chain.model.account import Account +from pyinjective.client.chain.model.auth_params import AuthParams +from pyinjective.client.model.pagination import PaginationOption + +from pyinjective.client.model.pagination import Pagination +from pyinjective.proto.cosmos.auth.v1beta1 import ( + query_pb2_grpc as auth_query_grpc, + query_pb2 as auth_query_pb, +) + + +class ChainGrpcAuthApi: + + def __init__(self, channel: Channel): + self._stub = auth_query_grpc.QueryStub(channel) + + async def fetch_module_params(self) -> AuthParams: + request = auth_query_pb.QueryParamsRequest() + response = await self._stub.Params(request) + + module_params = AuthParams.from_proto_response(response=response) + + return module_params + + async def fetch_account(self, address: str) -> Account: + request = auth_query_pb.QueryAccountRequest(address=address) + response = await self._stub.Account(request) + + account = Account.from_proto(proto_account=response.account) + + return account + + async def fetch_accounts(self, pagination_option: PaginationOption) -> Tuple[List[Account], PaginationOption]: + request = auth_query_pb.QueryAccountsRequest(pagination=pagination_option.create_pagination_request()) + response = await self._stub.Accounts(request) + + accounts = [Account.from_proto(proto_account=proto_account) for proto_account in response.accounts] + response_pagination = Pagination.from_proto(proto_pagination=response.pagination) + + return accounts, response_pagination \ No newline at end of file diff --git a/pyinjective/client/chain/model/__init__.py b/pyinjective/client/chain/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/model/account.py b/pyinjective/client/chain/model/account.py new file mode 100644 index 00000000..da497464 --- /dev/null +++ b/pyinjective/client/chain/model/account.py @@ -0,0 +1,42 @@ +from google.protobuf import any_pb2 + +from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb + +class Account: + + def __init__( + self, + address: str, + pub_key_type_url: str, + pub_key_value: bytes, + account_number: int, + sequence: int, + code_hash: str, + ): + super().__init__() + self.address = address + self.pub_key_type_url = pub_key_type_url + self.pub_key_value = pub_key_value + self.account_number = account_number + self.sequence = sequence + self.code_hash = code_hash + + @classmethod + def from_proto(cls, proto_account: any_pb2.Any): + eth_account = account_pb.EthAccount() + proto_account.Unpack(eth_account) + pub_key_type_url = None + pub_key_value = None + + if eth_account.base_account.pub_key is not None: + pub_key_type_url = eth_account.base_account.pub_key.type_url + pub_key_value = eth_account.base_account.pub_key.value + + return cls( + address=eth_account.base_account.address, + pub_key_type_url=pub_key_type_url, + pub_key_value=pub_key_value, + account_number=eth_account.base_account.account_number, + sequence=eth_account.base_account.sequence, + code_hash=f"0x{eth_account.code_hash.hex()}", + ) diff --git a/pyinjective/client/chain/model/auth_params.py b/pyinjective/client/chain/model/auth_params.py new file mode 100644 index 00000000..a19d92c6 --- /dev/null +++ b/pyinjective/client/chain/model/auth_params.py @@ -0,0 +1,31 @@ +from pyinjective.proto.cosmos.auth.v1beta1 import ( + query_pb2_grpc as auth_query_grpc, + query_pb2 as auth_query_pb, +) + +class AuthParams: + + def __init__( + self, + max_memo_characters: int, + tx_sig_limit: int, + tx_size_cost_per_byte: int, + sig_verify_cost_ed25519: int, + sig_verify_cost_secp256k1: int, + ): + super().__init__() + self.max_memo_characters = max_memo_characters + self.tx_sig_limit = tx_sig_limit + self.tx_size_cost_per_byte = tx_size_cost_per_byte + self.sig_verify_cost_ed25519 = sig_verify_cost_ed25519 + self.sig_verify_cost_secp256k1 = sig_verify_cost_secp256k1 + + @classmethod + def from_proto_response(cls, response: auth_query_pb.QueryParamsResponse): + return cls( + max_memo_characters=response.params.max_memo_characters, + tx_sig_limit=response.params.tx_sig_limit, + tx_size_cost_per_byte=response.params.tx_size_cost_per_byte, + sig_verify_cost_ed25519=response.params.sig_verify_cost_ed25519, + sig_verify_cost_secp256k1=response.params.sig_verify_cost_secp256k1 + ) \ No newline at end of file diff --git a/pyinjective/client/model/__init__.py b/pyinjective/client/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/model/pagination.py b/pyinjective/client/model/pagination.py new file mode 100644 index 00000000..c425b173 --- /dev/null +++ b/pyinjective/client/model/pagination.py @@ -0,0 +1,62 @@ +from typing import Optional + +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb + + +class PaginationOption: + + def __init__( + self, + key: Optional[str], + offset: Optional[int], + limit: Optional[int], + reverse: Optional[bool], + count_total: Optional[bool], + ): + super().__init__() + self.key = key + self.offset = offset + self.limit = limit + self.reverse = reverse + self.count_total = count_total + + def create_pagination_request(self) -> pagination_pb.PageRequest: + page_request = pagination_pb.PageRequest() + + if self.key is not None: + page_request.key = bytes.fromhex(self.key) + if self.offset is not None: + page_request.offset = self.offset + if self.limit is not None: + page_request.limit = self.limit + if self.reverse is not None: + page_request.reverse = self.reverse + if self.count_total is not None: + page_request.count_total = self.count_total + + return page_request + + +class Pagination: + + def __init__( + self, + next: Optional[str] = None, + total: Optional[int] = None, + ): + super().__init__() + self.next = next + self.total = total + + @classmethod + def from_proto(cls, proto_pagination: pagination_pb.PageResponse): + next = None + + if proto_pagination.next_key is not None: + next = f"0x{proto_pagination.next_key.hex()}" + total = proto_pagination.total + + return cls( + next=next, + total=total, + ) \ No newline at end of file diff --git a/tests/client/chain/grpc/configurable_auth_query_serciver.py b/tests/client/chain/grpc/configurable_auth_query_serciver.py new file mode 100644 index 00000000..7e856af9 --- /dev/null +++ b/tests/client/chain/grpc/configurable_auth_query_serciver.py @@ -0,0 +1,24 @@ +from collections import deque + +from pyinjective.proto.cosmos.auth.v1beta1 import ( + query_pb2_grpc as auth_query_grpc, + query_pb2 as auth_query_pb, +) + + +class ConfigurableAuthQueryServicer(auth_query_grpc.QueryServicer): + + def __init__(self): + super().__init__() + self.auth_params = deque() + self.account_responses = deque() + self.accounts_responses = deque() + + async def Params(self, request: auth_query_pb.QueryParamsRequest, context=None): + return self.auth_params.pop() + + async def Account(self, request: auth_query_pb.QueryAccountRequest, context=None): + return self.account_responses.pop() + + async def Accounts(self, request: auth_query_pb.QueryAccountsRequest, context=None): + return self.accounts_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_auth_api.py b/tests/client/chain/grpc/test_chain_grpc_auth_api.py new file mode 100644 index 00000000..71436e59 --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_auth_api.py @@ -0,0 +1,159 @@ +import grpc +import pytest +from google.protobuf import any_pb2 + +from pyinjective.client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.constant import Network +from pyinjective.proto.cosmos.auth.v1beta1 import ( + auth_pb2 as auth_pb, + query_pb2 as auth_query_pb, +) +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from pyinjective.proto.injective.crypto.v1beta1.ethsecp256k1 import keys_pb2 as keys_pb +from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb +from tests.client.chain.grpc.configurable_auth_query_serciver import ConfigurableAuthQueryServicer + +@pytest.fixture +def auth_servicer(): + return ConfigurableAuthQueryServicer() + + +class TestChainGrpcAuthApi: + + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + auth_servicer, + ): + params = auth_pb.Params( + max_memo_characters=256, + tx_sig_limit=7, + tx_size_cost_per_byte=10, + sig_verify_cost_ed25519=590, + sig_verify_cost_secp256k1=1000, + ) + auth_servicer.auth_params.append(auth_query_pb.QueryParamsResponse( + params=params + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthApi(channel=channel) + api._stub = auth_servicer + + module_params = await api.fetch_module_params() + + assert (params.max_memo_characters == module_params.max_memo_characters) + assert (params.tx_sig_limit == module_params.tx_sig_limit) + assert (params.tx_size_cost_per_byte == module_params.tx_size_cost_per_byte) + assert (params.sig_verify_cost_ed25519 == module_params.sig_verify_cost_ed25519) + assert (params.sig_verify_cost_secp256k1 == module_params.sig_verify_cost_secp256k1) + + @pytest.mark.asyncio + async def test_fetch_account( + self, + auth_servicer, + ): + pub_key = keys_pb.PubKey( + key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375" + ) + any_pub_key = any_pb2.Any() + any_pub_key.Pack(pub_key, type_url_prefix="") + + base_account = auth_pb.BaseAccount( + address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr", + pub_key=any_pub_key, + account_number=39, + sequence=697457, + ) + account = account_pb.EthAccount( + base_account=base_account, + code_hash=b"\305\322F\001\206\367#<\222~}\262\334\307\003\300\345\000\266S\312\202\';{\372\330\004]\205\244p" + ) + + any_account = any_pb2.Any() + any_account.Pack(account, type_url_prefix="") + auth_servicer.account_responses.append(auth_query_pb.QueryAccountResponse( + account=any_account + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthApi(channel=channel) + api._stub = auth_servicer + + response_account = await api.fetch_account(address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr") + + assert (f"0x{account.code_hash.hex()}" == response_account.code_hash) + assert (base_account.address == response_account.address) + assert (any_pub_key.type_url == response_account.pub_key_type_url) + assert (any_pub_key.value == response_account.pub_key_value) + assert (base_account.account_number == response_account.account_number) + assert (base_account.sequence == response_account.sequence) + + @pytest.mark.asyncio + async def test_fetch_accounts( + self, + auth_servicer, + ): + pub_key = keys_pb.PubKey( + key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375" + ) + any_pub_key = any_pb2.Any() + any_pub_key.Pack(pub_key, type_url_prefix="") + + base_account = auth_pb.BaseAccount( + address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr", + pub_key=any_pub_key, + account_number=39, + sequence=697457, + ) + account = account_pb.EthAccount( + base_account=base_account, + code_hash=b"\305\322F\001\206\367#<\222~}\262\334\307\003\300\345\000\266S\312\202\';{\372\330\004]\205\244p" + ) + + result_pagination = pagination_pb.PageResponse( + next_key=b"\001\032\264\007Z\224$]\377s8\343\004-\265\267\314?B\341", + total=16036, + ) + + any_account = any_pb2.Any() + any_account.Pack(account, type_url_prefix="") + auth_servicer.accounts_responses.append(auth_query_pb.QueryAccountsResponse( + accounts=[any_account], + pagination=result_pagination, + )) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthApi(channel=channel) + api._stub = auth_servicer + + pagination_option = PaginationOption( + key="011ab4075a94245dff7338e3042db5b7cc3f42e1", + offset=10, + limit=30, + reverse=False, + count_total=True, + ) + + response_accounts, response_pagination = await api.fetch_accounts(pagination_option=pagination_option) + + assert (1 == len(response_accounts)) + + response_account = response_accounts[0] + + assert (f"0x{account.code_hash.hex()}" == response_account.code_hash) + assert (base_account.address == response_account.address) + assert (any_pub_key.type_url == response_account.pub_key_type_url) + assert (any_pub_key.value == response_account.pub_key_value) + assert (base_account.account_number == response_account.account_number) + assert (base_account.sequence == response_account.sequence) + + assert (f"0x{result_pagination.next_key.hex()}" == response_pagination.next) + assert (result_pagination.total == response_pagination.total) \ No newline at end of file diff --git a/tests/client/chain/grpc/test_chain_grpc_bank_api.py b/tests/client/chain/grpc/test_chain_grpc_bank_api.py index b628916b..3e27e69c 100644 --- a/tests/client/chain/grpc/test_chain_grpc_bank_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_bank_api.py @@ -5,7 +5,6 @@ from pyinjective.constant import Network from pyinjective.proto.cosmos.bank.v1beta1 import ( bank_pb2 as bank_pb, - query_pb2_grpc as bank_query_grpc, query_pb2 as bank_query_pb, ) from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb From d4b51d128de3abffedf8893113890fb1400d2772 Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 11 Oct 2023 18:35:36 -0300 Subject: [PATCH 07/27] (fix) Fix Network imports in tests after synchronizing the refactoring branch with `dev`. Updated isort configuration to combine as imports --- pyinjective/async_client.py | 47 ++++--- .../chain/grpc/chain_grpc_auction_api.py | 5 +- .../client/chain/grpc/chain_grpc_auth_api.py | 12 +- .../client/chain/grpc/chain_grpc_bank_api.py | 17 +-- pyinjective/client/chain/model/account.py | 16 +-- pyinjective/client/chain/model/auth_params.py | 23 ++-- pyinjective/client/model/pagination.py | 22 ++- pyinjective/composer.py | 6 +- pyproject.toml | 7 +- .../configurable_auction_query_servicer.py | 3 +- .../grpc/configurable_auth_query_serciver.py | 6 +- .../grpc/configurable_bank_query_servicer.py | 6 +- .../chain/grpc/test_chain_grpc_auction_api.py | 70 ++++------ .../chain/grpc/test_chain_grpc_auth_api.py | 93 ++++++------- .../chain/grpc/test_chain_grpc_bank_api.py | 126 ++++++++---------- 15 files changed, 196 insertions(+), 263 deletions(-) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 8f1a7194..d666c1e7 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -16,31 +16,30 @@ from .core.network import Network from .core.token import Token from .exceptions import NotFoundError -from .proto.cosmos.authz.v1beta1 import query_pb2 as authz_query -from .proto.cosmos.authz.v1beta1 import query_pb2_grpc as authz_query_grpc +from .proto.cosmos.authz.v1beta1 import query_pb2 as authz_query, query_pb2_grpc as authz_query_grpc from .proto.cosmos.base.abci.v1beta1 import abci_pb2 as abci_type -from .proto.cosmos.base.tendermint.v1beta1 import query_pb2 as tendermint_query -from .proto.cosmos.base.tendermint.v1beta1 import query_pb2_grpc as tendermint_query_grpc -from .proto.cosmos.tx.v1beta1 import service_pb2 as tx_service -from .proto.cosmos.tx.v1beta1 import service_pb2_grpc as tx_service_grpc -from .proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_rpc_pb -from .proto.exchange import injective_accounts_rpc_pb2_grpc as exchange_accounts_rpc_grpc -from .proto.exchange import injective_auction_rpc_pb2 as auction_rpc_pb -from .proto.exchange import injective_auction_rpc_pb2_grpc as auction_rpc_grpc -from .proto.exchange import injective_derivative_exchange_rpc_pb2 as derivative_exchange_rpc_pb -from .proto.exchange import injective_derivative_exchange_rpc_pb2_grpc as derivative_exchange_rpc_grpc -from .proto.exchange import injective_explorer_rpc_pb2 as explorer_rpc_pb -from .proto.exchange import injective_explorer_rpc_pb2_grpc as explorer_rpc_grpc -from .proto.exchange import injective_insurance_rpc_pb2 as insurance_rpc_pb -from .proto.exchange import injective_insurance_rpc_pb2_grpc as insurance_rpc_grpc -from .proto.exchange import injective_meta_rpc_pb2 as exchange_meta_rpc_pb -from .proto.exchange import injective_meta_rpc_pb2_grpc as exchange_meta_rpc_grpc -from .proto.exchange import injective_oracle_rpc_pb2 as oracle_rpc_pb -from .proto.exchange import injective_oracle_rpc_pb2_grpc as oracle_rpc_grpc -from .proto.exchange import injective_portfolio_rpc_pb2 as portfolio_rpc_pb -from .proto.exchange import injective_portfolio_rpc_pb2_grpc as portfolio_rpc_grpc -from .proto.exchange import injective_spot_exchange_rpc_pb2 as spot_exchange_rpc_pb -from .proto.exchange import injective_spot_exchange_rpc_pb2_grpc as spot_exchange_rpc_grpc +from .proto.cosmos.base.tendermint.v1beta1 import query_pb2 as tendermint_query, query_pb2_grpc as tendermint_query_grpc +from .proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, service_pb2_grpc as tx_service_grpc +from .proto.exchange import ( + injective_accounts_rpc_pb2 as exchange_accounts_rpc_pb, + injective_accounts_rpc_pb2_grpc as exchange_accounts_rpc_grpc, + injective_auction_rpc_pb2 as auction_rpc_pb, + injective_auction_rpc_pb2_grpc as auction_rpc_grpc, + injective_derivative_exchange_rpc_pb2 as derivative_exchange_rpc_pb, + injective_derivative_exchange_rpc_pb2_grpc as derivative_exchange_rpc_grpc, + injective_explorer_rpc_pb2 as explorer_rpc_pb, + injective_explorer_rpc_pb2_grpc as explorer_rpc_grpc, + injective_insurance_rpc_pb2 as insurance_rpc_pb, + injective_insurance_rpc_pb2_grpc as insurance_rpc_grpc, + injective_meta_rpc_pb2 as exchange_meta_rpc_pb, + injective_meta_rpc_pb2_grpc as exchange_meta_rpc_grpc, + injective_oracle_rpc_pb2 as oracle_rpc_pb, + injective_oracle_rpc_pb2_grpc as oracle_rpc_grpc, + injective_portfolio_rpc_pb2 as portfolio_rpc_pb, + injective_portfolio_rpc_pb2_grpc as portfolio_rpc_grpc, + injective_spot_exchange_rpc_pb2 as spot_exchange_rpc_pb, + injective_spot_exchange_rpc_pb2_grpc as spot_exchange_rpc_grpc, +) from .proto.injective.types.v1beta1 import account_pb2 from .utils.logger import LoggerProvider diff --git a/pyinjective/client/chain/grpc/chain_grpc_auction_api.py b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py index 1e54cc27..d1fd6e89 100644 --- a/pyinjective/client/chain/grpc/chain_grpc_auction_api.py +++ b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py @@ -1,15 +1,14 @@ -from typing import Dict, Any +from typing import Any, Dict from grpc.aio import Channel from pyinjective.proto.injective.auction.v1beta1 import ( - query_pb2_grpc as auction_query_grpc, query_pb2 as auction_query_pb, + query_pb2_grpc as auction_query_grpc, ) class ChainGrpcAuctionApi: - def __init__(self, channel: Channel): self._stub = auction_query_grpc.QueryStub(channel) diff --git a/pyinjective/client/chain/grpc/chain_grpc_auth_api.py b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py index c9265029..45039288 100644 --- a/pyinjective/client/chain/grpc/chain_grpc_auth_api.py +++ b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py @@ -4,17 +4,11 @@ from pyinjective.client.chain.model.account import Account from pyinjective.client.chain.model.auth_params import AuthParams -from pyinjective.client.model.pagination import PaginationOption - -from pyinjective.client.model.pagination import Pagination -from pyinjective.proto.cosmos.auth.v1beta1 import ( - query_pb2_grpc as auth_query_grpc, - query_pb2 as auth_query_pb, -) +from pyinjective.client.model.pagination import Pagination, PaginationOption +from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query_pb, query_pb2_grpc as auth_query_grpc class ChainGrpcAuthApi: - def __init__(self, channel: Channel): self._stub = auth_query_grpc.QueryStub(channel) @@ -41,4 +35,4 @@ async def fetch_accounts(self, pagination_option: PaginationOption) -> Tuple[Lis accounts = [Account.from_proto(proto_account=proto_account) for proto_account in response.accounts] response_pagination = Pagination.from_proto(proto_pagination=response.pagination) - return accounts, response_pagination \ No newline at end of file + return accounts, response_pagination diff --git a/pyinjective/client/chain/grpc/chain_grpc_bank_api.py b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py index 7e916fba..5e42917e 100644 --- a/pyinjective/client/chain/grpc/chain_grpc_bank_api.py +++ b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py @@ -1,14 +1,11 @@ -from typing import Dict, Any +from typing import Any, Dict from grpc.aio import Channel -from pyinjective.proto.cosmos.bank.v1beta1 import ( - query_pb2_grpc as bank_query_grpc, - query_pb2 as bank_query_pb, -) +from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query_pb, query_pb2_grpc as bank_query_grpc -class ChainGrpcBankApi: +class ChainGrpcBankApi: def __init__(self, channel: Channel): self._stub = bank_query_grpc.QueryStub(channel) @@ -39,7 +36,8 @@ async def fetch_balances(self, account_address: str) -> Dict[str, Any]: bank_balances = { "balances": [{"amount": coin.amount, "denom": coin.denom} for coin in response.balances], - "pagination": {"total": response.pagination.total}} + "pagination": {"total": response.pagination.total}, + } return bank_balances @@ -49,10 +47,7 @@ async def fetch_total_supply(self) -> Dict[str, Any]: total_supply = { "supply": [{"amount": coin.amount, "denom": coin.denom} for coin in response.supply], - "pagination": { - "next": response.pagination.next_key, - "total": response.pagination.total - } + "pagination": {"next": response.pagination.next_key, "total": response.pagination.total}, } return total_supply diff --git a/pyinjective/client/chain/model/account.py b/pyinjective/client/chain/model/account.py index da497464..5aa401c3 100644 --- a/pyinjective/client/chain/model/account.py +++ b/pyinjective/client/chain/model/account.py @@ -2,16 +2,16 @@ from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb -class Account: +class Account: def __init__( - self, - address: str, - pub_key_type_url: str, - pub_key_value: bytes, - account_number: int, - sequence: int, - code_hash: str, + self, + address: str, + pub_key_type_url: str, + pub_key_value: bytes, + account_number: int, + sequence: int, + code_hash: str, ): super().__init__() self.address = address diff --git a/pyinjective/client/chain/model/auth_params.py b/pyinjective/client/chain/model/auth_params.py index a19d92c6..4d238cff 100644 --- a/pyinjective/client/chain/model/auth_params.py +++ b/pyinjective/client/chain/model/auth_params.py @@ -1,17 +1,14 @@ -from pyinjective.proto.cosmos.auth.v1beta1 import ( - query_pb2_grpc as auth_query_grpc, - query_pb2 as auth_query_pb, -) +from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query_pb -class AuthParams: +class AuthParams: def __init__( - self, - max_memo_characters: int, - tx_sig_limit: int, - tx_size_cost_per_byte: int, - sig_verify_cost_ed25519: int, - sig_verify_cost_secp256k1: int, + self, + max_memo_characters: int, + tx_sig_limit: int, + tx_size_cost_per_byte: int, + sig_verify_cost_ed25519: int, + sig_verify_cost_secp256k1: int, ): super().__init__() self.max_memo_characters = max_memo_characters @@ -27,5 +24,5 @@ def from_proto_response(cls, response: auth_query_pb.QueryParamsResponse): tx_sig_limit=response.params.tx_sig_limit, tx_size_cost_per_byte=response.params.tx_size_cost_per_byte, sig_verify_cost_ed25519=response.params.sig_verify_cost_ed25519, - sig_verify_cost_secp256k1=response.params.sig_verify_cost_secp256k1 - ) \ No newline at end of file + sig_verify_cost_secp256k1=response.params.sig_verify_cost_secp256k1, + ) diff --git a/pyinjective/client/model/pagination.py b/pyinjective/client/model/pagination.py index c425b173..9a2d9ba8 100644 --- a/pyinjective/client/model/pagination.py +++ b/pyinjective/client/model/pagination.py @@ -4,14 +4,13 @@ class PaginationOption: - def __init__( - self, - key: Optional[str], - offset: Optional[int], - limit: Optional[int], - reverse: Optional[bool], - count_total: Optional[bool], + self, + key: Optional[str], + offset: Optional[int], + limit: Optional[int], + reverse: Optional[bool], + count_total: Optional[bool], ): super().__init__() self.key = key @@ -38,11 +37,10 @@ def create_pagination_request(self) -> pagination_pb.PageRequest: class Pagination: - def __init__( - self, - next: Optional[str] = None, - total: Optional[int] = None, + self, + next: Optional[str] = None, + total: Optional[int] = None, ): super().__init__() self.next = next @@ -59,4 +57,4 @@ def from_proto(cls, proto_pagination: pagination_pb.PageResponse): return cls( next=next, total=total, - ) \ No newline at end of file + ) diff --git a/pyinjective/composer.py b/pyinjective/composer.py index b543b077..009fd1e9 100644 --- a/pyinjective/composer.py +++ b/pyinjective/composer.py @@ -15,16 +15,14 @@ from .constant import ADDITIONAL_CHAIN_FORMAT_DECIMALS, INJ_DENOM from .core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket from .core.token import Token -from .proto.cosmos.authz.v1beta1 import authz_pb2 as cosmos_authz_pb -from .proto.cosmos.authz.v1beta1 import tx_pb2 as cosmos_authz_tx_pb +from .proto.cosmos.authz.v1beta1 import authz_pb2 as cosmos_authz_pb, tx_pb2 as cosmos_authz_tx_pb from .proto.cosmos.bank.v1beta1 import tx_pb2 as cosmos_bank_tx_pb from .proto.cosmos.distribution.v1beta1 import tx_pb2 as cosmos_distribution_tx_pb from .proto.cosmos.gov.v1beta1 import tx_pb2 as cosmos_gov_tx_pb from .proto.cosmos.staking.v1beta1 import tx_pb2 as cosmos_staking_tx_pb from .proto.cosmwasm.wasm.v1 import tx_pb2 as wasm_tx_pb from .proto.injective.auction.v1beta1 import tx_pb2 as injective_auction_tx_pb -from .proto.injective.exchange.v1beta1 import authz_pb2 as injective_authz_pb -from .proto.injective.exchange.v1beta1 import tx_pb2 as injective_exchange_tx_pb +from .proto.injective.exchange.v1beta1 import authz_pb2 as injective_authz_pb, tx_pb2 as injective_exchange_tx_pb from .proto.injective.insurance.v1beta1 import tx_pb2 as injective_insurance_tx_pb from .proto.injective.oracle.v1beta1 import tx_pb2 as injective_oracle_tx_pb from .proto.injective.peggy.v1 import msgs_pb2 as injective_peggy_tx_pb diff --git a/pyproject.toml b/pyproject.toml index 737dbd6a..9660cf8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,8 +71,13 @@ pyflakes = ["-F811"] # disable a plugin [tool.isort] -profile = "black" line_length = 120 +multi_line_output = 3 +combine_as_imports = true +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true skip_glob = ["pyinjective/proto/*", ".idea/*"] diff --git a/tests/client/chain/grpc/configurable_auction_query_servicer.py b/tests/client/chain/grpc/configurable_auction_query_servicer.py index aa7ba51a..c3879d59 100644 --- a/tests/client/chain/grpc/configurable_auction_query_servicer.py +++ b/tests/client/chain/grpc/configurable_auction_query_servicer.py @@ -1,13 +1,12 @@ from collections import deque from pyinjective.proto.injective.auction.v1beta1 import ( - query_pb2_grpc as auction_query_grpc, query_pb2 as auction_query_pb, + query_pb2_grpc as auction_query_grpc, ) class ConfigurableAuctionQueryServicer(auction_query_grpc.QueryServicer): - def __init__(self): super().__init__() self.auction_params = deque() diff --git a/tests/client/chain/grpc/configurable_auth_query_serciver.py b/tests/client/chain/grpc/configurable_auth_query_serciver.py index 7e856af9..52b7c560 100644 --- a/tests/client/chain/grpc/configurable_auth_query_serciver.py +++ b/tests/client/chain/grpc/configurable_auth_query_serciver.py @@ -1,13 +1,9 @@ from collections import deque -from pyinjective.proto.cosmos.auth.v1beta1 import ( - query_pb2_grpc as auth_query_grpc, - query_pb2 as auth_query_pb, -) +from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query_pb, query_pb2_grpc as auth_query_grpc class ConfigurableAuthQueryServicer(auth_query_grpc.QueryServicer): - def __init__(self): super().__init__() self.auth_params = deque() diff --git a/tests/client/chain/grpc/configurable_bank_query_servicer.py b/tests/client/chain/grpc/configurable_bank_query_servicer.py index c9c27968..0f127763 100644 --- a/tests/client/chain/grpc/configurable_bank_query_servicer.py +++ b/tests/client/chain/grpc/configurable_bank_query_servicer.py @@ -1,13 +1,9 @@ from collections import deque -from pyinjective.proto.cosmos.bank.v1beta1 import ( - query_pb2_grpc as bank_query_grpc, - query_pb2 as bank_query_pb, -) +from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query_pb, query_pb2_grpc as bank_query_grpc class ConfigurableBankQueryServicer(bank_query_grpc.QueryServicer): - def __init__(self): super().__init__() self.bank_params = deque() diff --git a/tests/client/chain/grpc/test_chain_grpc_auction_api.py b/tests/client/chain/grpc/test_chain_grpc_auction_api.py index 3e55b3cf..8f99f1ce 100644 --- a/tests/client/chain/grpc/test_chain_grpc_auction_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_auction_api.py @@ -2,7 +2,7 @@ import pytest from pyinjective.client.chain.grpc.chain_grpc_auction_api import ChainGrpcAuctionApi -from pyinjective.constant import Network +from pyinjective.core.network import Network from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb from pyinjective.proto.injective.auction.v1beta1 import ( auction_pb2 as auction_pb, @@ -18,19 +18,13 @@ def auction_servicer(): class TestChainGrpcAuctionApi: - @pytest.mark.asyncio async def test_fetch_module_params( - self, - auction_servicer, + self, + auction_servicer, ): - params = auction_pb.Params( - auction_period=604800, - min_next_bid_increment_rate="2500000000000000" - ) - auction_servicer.auction_params.append(auction_query_pb.QueryAuctionParamsResponse( - params=params - )) + params = auction_pb.Params(auction_period=604800, min_next_bid_increment_rate="2500000000000000") + auction_servicer.auction_params.append(auction_query_pb.QueryAuctionParamsResponse(params=params)) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -44,17 +38,14 @@ async def test_fetch_module_params( "min_next_bid_increment_rate": "2500000000000000", } - assert (expected_params == module_params) + assert expected_params == module_params @pytest.mark.asyncio async def test_fetch_module_state( - self, - auction_servicer, + self, + auction_servicer, ): - params = auction_pb.Params( - auction_period=604800, - min_next_bid_increment_rate="2500000000000000" - ) + params = auction_pb.Params(auction_period=604800, min_next_bid_increment_rate="2500000000000000") highest_bid = auction_pb.Bid( bidder="inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", amount="\n\003inj\022\0232347518723906280000", @@ -65,9 +56,7 @@ async def test_fetch_module_state( highest_bid=highest_bid, auction_ending_timestamp=1687504387, ) - auction_servicer.module_states.append(auction_query_pb.QueryModuleStateResponse( - state=state - )) + auction_servicer.module_states.append(auction_query_pb.QueryModuleStateResponse(state=state)) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -89,25 +78,20 @@ async def test_fetch_module_state( "auction_ending_timestamp": 1687504387, } - assert (expected_state == module_state) + assert expected_state == module_state @pytest.mark.asyncio async def test_fetch_module_state_when_no_highest_bid_present( - self, - auction_servicer, + self, + auction_servicer, ): - params = auction_pb.Params( - auction_period=604800, - min_next_bid_increment_rate="2500000000000000" - ) + params = auction_pb.Params(auction_period=604800, min_next_bid_increment_rate="2500000000000000") state = genesis_pb.GenesisState( params=params, auction_round=50, auction_ending_timestamp=1687504387, ) - auction_servicer.module_states.append(auction_query_pb.QueryModuleStateResponse( - state=state - )) + auction_servicer.module_states.append(auction_query_pb.QueryModuleStateResponse(state=state)) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -129,12 +113,12 @@ async def test_fetch_module_state_when_no_highest_bid_present( "auction_ending_timestamp": 1687504387, } - assert (expected_state == module_state) + assert expected_state == module_state @pytest.mark.asyncio async def test_fetch_current_basket( - self, - auction_servicer, + self, + auction_servicer, ): first_amount = coin_pb.Coin( amount="15059786755", @@ -145,13 +129,15 @@ async def test_fetch_current_basket( denom="peggy0xf9152067989BDc8783fF586624124C05A529A5D1", ) - auction_servicer.current_baskets.append(auction_query_pb.QueryCurrentAuctionBasketResponse( - amount=[first_amount, second_amount], - auctionRound=50, - auctionClosingTime=1687504387, - highestBidder="inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", - highestBidAmount="2347518723906280000", - )) + auction_servicer.current_baskets.append( + auction_query_pb.QueryCurrentAuctionBasketResponse( + amount=[first_amount, second_amount], + auctionRound=50, + auctionClosingTime=1687504387, + highestBidder="inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + highestBidAmount="2347518723906280000", + ) + ) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -168,4 +154,4 @@ async def test_fetch_current_basket( "highest_bid_amount": "2347518723906280000", } - assert (expected_basket == current_basket) + assert expected_basket == current_basket diff --git a/tests/client/chain/grpc/test_chain_grpc_auth_api.py b/tests/client/chain/grpc/test_chain_grpc_auth_api.py index 71436e59..32e44d74 100644 --- a/tests/client/chain/grpc/test_chain_grpc_auth_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_auth_api.py @@ -4,27 +4,24 @@ from pyinjective.client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi from pyinjective.client.model.pagination import PaginationOption -from pyinjective.constant import Network -from pyinjective.proto.cosmos.auth.v1beta1 import ( - auth_pb2 as auth_pb, - query_pb2 as auth_query_pb, -) +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.auth.v1beta1 import auth_pb2 as auth_pb, query_pb2 as auth_query_pb from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb from pyinjective.proto.injective.crypto.v1beta1.ethsecp256k1 import keys_pb2 as keys_pb from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb from tests.client.chain.grpc.configurable_auth_query_serciver import ConfigurableAuthQueryServicer + @pytest.fixture def auth_servicer(): return ConfigurableAuthQueryServicer() class TestChainGrpcAuthApi: - @pytest.mark.asyncio async def test_fetch_module_params( - self, - auth_servicer, + self, + auth_servicer, ): params = auth_pb.Params( max_memo_characters=256, @@ -33,9 +30,7 @@ async def test_fetch_module_params( sig_verify_cost_ed25519=590, sig_verify_cost_secp256k1=1000, ) - auth_servicer.auth_params.append(auth_query_pb.QueryParamsResponse( - params=params - )) + auth_servicer.auth_params.append(auth_query_pb.QueryParamsResponse(params=params)) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -45,20 +40,18 @@ async def test_fetch_module_params( module_params = await api.fetch_module_params() - assert (params.max_memo_characters == module_params.max_memo_characters) - assert (params.tx_sig_limit == module_params.tx_sig_limit) - assert (params.tx_size_cost_per_byte == module_params.tx_size_cost_per_byte) - assert (params.sig_verify_cost_ed25519 == module_params.sig_verify_cost_ed25519) - assert (params.sig_verify_cost_secp256k1 == module_params.sig_verify_cost_secp256k1) + assert params.max_memo_characters == module_params.max_memo_characters + assert params.tx_sig_limit == module_params.tx_sig_limit + assert params.tx_size_cost_per_byte == module_params.tx_size_cost_per_byte + assert params.sig_verify_cost_ed25519 == module_params.sig_verify_cost_ed25519 + assert params.sig_verify_cost_secp256k1 == module_params.sig_verify_cost_secp256k1 @pytest.mark.asyncio async def test_fetch_account( - self, - auth_servicer, + self, + auth_servicer, ): - pub_key = keys_pb.PubKey( - key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375" - ) + pub_key = keys_pb.PubKey(key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375") any_pub_key = any_pb2.Any() any_pub_key.Pack(pub_key, type_url_prefix="") @@ -70,14 +63,13 @@ async def test_fetch_account( ) account = account_pb.EthAccount( base_account=base_account, - code_hash=b"\305\322F\001\206\367#<\222~}\262\334\307\003\300\345\000\266S\312\202\';{\372\330\004]\205\244p" + code_hash=b"\305\322F\001\206\367#<\222~}\262\334\307\003\300\345\000\266S\312\202';{" + b"\372\330\004]\205\244p", ) any_account = any_pb2.Any() any_account.Pack(account, type_url_prefix="") - auth_servicer.account_responses.append(auth_query_pb.QueryAccountResponse( - account=any_account - )) + auth_servicer.account_responses.append(auth_query_pb.QueryAccountResponse(account=any_account)) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -87,21 +79,19 @@ async def test_fetch_account( response_account = await api.fetch_account(address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr") - assert (f"0x{account.code_hash.hex()}" == response_account.code_hash) - assert (base_account.address == response_account.address) - assert (any_pub_key.type_url == response_account.pub_key_type_url) - assert (any_pub_key.value == response_account.pub_key_value) - assert (base_account.account_number == response_account.account_number) - assert (base_account.sequence == response_account.sequence) + assert f"0x{account.code_hash.hex()}" == response_account.code_hash + assert base_account.address == response_account.address + assert any_pub_key.type_url == response_account.pub_key_type_url + assert any_pub_key.value == response_account.pub_key_value + assert base_account.account_number == response_account.account_number + assert base_account.sequence == response_account.sequence @pytest.mark.asyncio async def test_fetch_accounts( - self, - auth_servicer, + self, + auth_servicer, ): - pub_key = keys_pb.PubKey( - key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375" - ) + pub_key = keys_pb.PubKey(key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375") any_pub_key = any_pb2.Any() any_pub_key.Pack(pub_key, type_url_prefix="") @@ -113,7 +103,8 @@ async def test_fetch_accounts( ) account = account_pb.EthAccount( base_account=base_account, - code_hash=b"\305\322F\001\206\367#<\222~}\262\334\307\003\300\345\000\266S\312\202\';{\372\330\004]\205\244p" + code_hash=b"\305\322F\001\206\367#<\222~}\262\334\307\003\300\345\000\266S\312\202';{" + b"\372\330\004]\205\244p", ) result_pagination = pagination_pb.PageResponse( @@ -123,10 +114,12 @@ async def test_fetch_accounts( any_account = any_pb2.Any() any_account.Pack(account, type_url_prefix="") - auth_servicer.accounts_responses.append(auth_query_pb.QueryAccountsResponse( - accounts=[any_account], - pagination=result_pagination, - )) + auth_servicer.accounts_responses.append( + auth_query_pb.QueryAccountsResponse( + accounts=[any_account], + pagination=result_pagination, + ) + ) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -144,16 +137,16 @@ async def test_fetch_accounts( response_accounts, response_pagination = await api.fetch_accounts(pagination_option=pagination_option) - assert (1 == len(response_accounts)) + assert 1 == len(response_accounts) response_account = response_accounts[0] - assert (f"0x{account.code_hash.hex()}" == response_account.code_hash) - assert (base_account.address == response_account.address) - assert (any_pub_key.type_url == response_account.pub_key_type_url) - assert (any_pub_key.value == response_account.pub_key_value) - assert (base_account.account_number == response_account.account_number) - assert (base_account.sequence == response_account.sequence) + assert f"0x{account.code_hash.hex()}" == response_account.code_hash + assert base_account.address == response_account.address + assert any_pub_key.type_url == response_account.pub_key_type_url + assert any_pub_key.value == response_account.pub_key_value + assert base_account.account_number == response_account.account_number + assert base_account.sequence == response_account.sequence - assert (f"0x{result_pagination.next_key.hex()}" == response_pagination.next) - assert (result_pagination.total == response_pagination.total) \ No newline at end of file + assert f"0x{result_pagination.next_key.hex()}" == response_pagination.next + assert result_pagination.total == response_pagination.total diff --git a/tests/client/chain/grpc/test_chain_grpc_bank_api.py b/tests/client/chain/grpc/test_chain_grpc_bank_api.py index 3e27e69c..e08c15ee 100644 --- a/tests/client/chain/grpc/test_chain_grpc_bank_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_bank_api.py @@ -2,13 +2,10 @@ import pytest from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi -from pyinjective.constant import Network -from pyinjective.proto.cosmos.bank.v1beta1 import ( - bank_pb2 as bank_pb, - query_pb2 as bank_query_pb, -) -from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.bank.v1beta1 import bank_pb2 as bank_pb, query_pb2 as bank_query_pb from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer @@ -18,16 +15,13 @@ def bank_servicer(): class TestChainGrpcBankApi: - @pytest.mark.asyncio async def test_fetch_module_params( - self, - bank_servicer, + self, + bank_servicer, ): params = bank_pb.Params(default_send_enabled=True) - bank_servicer.bank_params.append(bank_query_pb.QueryParamsResponse( - params=params - )) + bank_servicer.bank_params.append(bank_query_pb.QueryParamsResponse(params=params)) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -40,20 +34,15 @@ async def test_fetch_module_params( "default_send_enabled": True, } - assert (expected_params == module_params) + assert expected_params == module_params @pytest.mark.asyncio async def test_fetch_balance( - self, - bank_servicer, + self, + bank_servicer, ): - balance = coin_pb.Coin( - denom="inj", - amount="988987297011197594664" - ) - bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse( - balance=balance - )) + balance = coin_pb.Coin(denom="inj", amount="988987297011197594664") + bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse(balance=balance)) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -62,28 +51,19 @@ async def test_fetch_balance( api._stub = bank_servicer bank_balance = await api.fetch_balance( - account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", - denom="inj" + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", denom="inj" ) - expected_balance = { - "denom": "inj", - "amount": "988987297011197594664" - } + expected_balance = {"denom": "inj", "amount": "988987297011197594664"} - assert (expected_balance == bank_balance) + assert expected_balance == bank_balance @pytest.mark.asyncio async def test_fetch_balance( - self, - bank_servicer, + self, + bank_servicer, ): - balance = coin_pb.Coin( - denom="inj", - amount="988987297011197594664" - ) - bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse( - balance=balance - )) + balance = coin_pb.Coin(denom="inj", amount="988987297011197594664") + bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse(balance=balance)) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -92,35 +72,27 @@ async def test_fetch_balance( api._stub = bank_servicer bank_balance = await api.fetch_balance( - account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", - denom="inj" + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", denom="inj" ) - expected_balance = { - "denom": "inj", - "amount": "988987297011197594664" - } + expected_balance = {"denom": "inj", "amount": "988987297011197594664"} - assert (expected_balance == bank_balance) + assert expected_balance == bank_balance @pytest.mark.asyncio async def test_fetch_balances( - self, - bank_servicer, + self, + bank_servicer, ): - first_balance = coin_pb.Coin( - denom="inj", - amount="988987297011197594664" - ) - second_balance = coin_pb.Coin( - denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", - amount="54497408" - ) + first_balance = coin_pb.Coin(denom="inj", amount="988987297011197594664") + second_balance = coin_pb.Coin(denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", amount="54497408") pagination = pagination_pb.PageResponse(total=2) - bank_servicer.balances_responses.append(bank_query_pb.QueryAllBalancesResponse( - balances=[first_balance, second_balance], - pagination=pagination, - )) + bank_servicer.balances_responses.append( + bank_query_pb.QueryAllBalancesResponse( + balances=[first_balance, second_balance], + pagination=pagination, + ) + ) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -136,30 +108,33 @@ async def test_fetch_balances( "pagination": {"total": 2}, } - assert (expected_balances == bank_balances) + assert expected_balances == bank_balances @pytest.mark.asyncio async def test_fetch_total_supply( - self, - bank_servicer, + self, + bank_servicer, ): first_supply = coin_pb.Coin( - denom="factory/inj108t3mlej0dph8er6ca2lq5cs9pdgzva5mqsn5p/position", - amount="5556700000000000000" + denom="factory/inj108t3mlej0dph8er6ca2lq5cs9pdgzva5mqsn5p/position", amount="5556700000000000000" ) second_supply = coin_pb.Coin( denom="factory/inj10uycavvkc4uqr8tns3599r0t2xux4rz3y8apym/1684002313InjUsdt1d110C", - amount="1123456789111100000" + amount="1123456789111100000", ) pagination = pagination_pb.PageResponse( - next_key="factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja".encode(), - total=179 + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, ) - bank_servicer.total_supply_responses.append(bank_query_pb.QueryTotalSupplyResponse( - supply=[first_supply, second_supply], - pagination=pagination, - )) + bank_servicer.total_supply_responses.append( + bank_query_pb.QueryTotalSupplyResponse( + supply=[first_supply, second_supply], + pagination=pagination, + ) + ) network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) @@ -171,8 +146,11 @@ async def test_fetch_total_supply( expected_supply = { "supply": [{"denom": coin.denom, "amount": coin.amount} for coin in [first_supply, second_supply]], "pagination": { - "next": "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja".encode(), - "total": 179}, + "next": ( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + "total": 179, + }, } - assert (expected_supply == total_supply) + assert expected_supply == total_supply From ba7c0ab8f7577afc4c00beb221023a771c843808 Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 20 Oct 2023 11:25:19 -0300 Subject: [PATCH 08/27] (feat) Implemented Indexer Account low level API component. Created new functions in AsyncClient to use the new component and marked the old ones as deprecated (to be removed in the future) --- examples/chain_client/0_LocalOrderHash.py | 2 +- .../13_MsgIncreasePositionMargin.py | 2 +- examples/chain_client/15_MsgWithdraw.py | 2 +- .../chain_client/16_MsgSubaccountTransfer.py | 2 +- .../chain_client/17_MsgBatchUpdateOrders.py | 2 +- examples/chain_client/18_MsgBid.py | 2 +- examples/chain_client/19_MsgGrant.py | 2 +- examples/chain_client/1_MsgSend.py | 2 +- examples/chain_client/20_MsgExec.py | 2 +- examples/chain_client/21_MsgRevoke.py | 2 +- examples/chain_client/22_MsgSendToEth.py | 2 +- .../chain_client/23_MsgRelayPriceFeedPrice.py | 2 +- examples/chain_client/24_MsgRewardsOptOut.py | 2 +- examples/chain_client/25_MsgDelegate.py | 2 +- .../26_MsgWithdrawDelegatorReward.py | 2 +- examples/chain_client/28_BankBalances.py | 2 +- examples/chain_client/29_BankBalance.py | 2 +- examples/chain_client/2_MsgDeposit.py | 2 +- examples/chain_client/30_ExternalTransfer.py | 2 +- .../31_MsgCreateBinaryOptionsLimitOrder.py | 2 +- .../32_MsgCreateBinaryOptionsMarketOrder.py | 2 +- .../33_MsgCancelBinaryOptionsOrder.py | 2 +- .../34_MsgAdminUpdateBinaryOptionsMarket.py | 2 +- .../35_MsgInstantBinaryOptionsMarketLaunch.py | 2 +- .../chain_client/36_MsgRelayProviderPrices.py | 2 +- examples/chain_client/39_Account.py | 2 +- .../chain_client/3_MsgCreateSpotLimitOrder.py | 2 +- .../chain_client/40_MsgExecuteContract.py | 2 +- .../chain_client/41_MsgCreateInsuranceFund.py | 2 +- examples/chain_client/42_MsgUnderwrite.py | 2 +- .../chain_client/43_MsgRequestRedemption.py | 2 +- ...8_WithdrawValidatorCommissionAndRewards.py | 2 +- .../4_MsgCreateSpotMarketOrder.py | 2 +- examples/chain_client/5_MsgCancelSpotOrder.py | 2 +- .../6_MsgCreateDerivativeLimitOrder.py | 2 +- .../7_MsgCreateDerivativeMarketOrder.py | 2 +- .../8_MsgCancelDerivativeOrder.py | 2 +- .../accounts_rpc/1_StreamSubaccountBalance.py | 31 +- .../accounts_rpc/2_SubaccountBalance.py | 2 +- .../accounts_rpc/3_SubaccountsList.py | 2 +- .../accounts_rpc/5_SubaccountHistory.py | 2 +- .../accounts_rpc/6_SubaccountOrderSummary.py | 2 +- .../accounts_rpc/7_OrderStates.py | 2 +- .../accounts_rpc/8_Portfolio.py | 2 +- .../exchange_client/accounts_rpc/9_Rewards.py | 7 +- poetry.lock | 16 +- pyinjective/async_client.py | 234 ++++++++-- .../chain/grpc/chain_grpc_auction_api.py | 53 +-- .../client/chain/grpc/chain_grpc_auth_api.py | 36 +- .../client/chain/grpc/chain_grpc_bank_api.py | 42 +- pyinjective/client/indexer/__init__.py | 0 pyinjective/client/indexer/grpc/__init__.py | 0 .../indexer/grpc/indexer_grpc_account_api.py | 107 +++++ .../client/indexer/grpc_stream/__init__.py | 0 .../indexer_grpc_account_stream.py | 55 +++ pyinjective/core/broadcaster.py | 2 +- .../utils/grpc_api_request_assistant.py | 20 + pyproject.toml | 2 +- .../configurable_auction_query_servicer.py | 8 +- .../grpc/configurable_auth_query_serciver.py | 6 +- .../grpc/configurable_bank_query_servicer.py | 8 +- .../chain/grpc/test_chain_grpc_auction_api.py | 75 ++-- .../chain/grpc/test_chain_grpc_auth_api.py | 72 ++-- .../chain/grpc/test_chain_grpc_bank_api.py | 36 +- tests/client/indexer/__init__.py | 0 .../configurable_account_query_servicer.py | 58 +++ tests/client/indexer/grpc/__init__.py | 0 .../grpc/test_indexer_grpc_account_api.py | 398 ++++++++++++++++++ tests/client/indexer/stream_grpc/__init__.py | 0 .../test_indexer_grpc_account_stream.py | 78 ++++ tests/test_async_client.py | 2 +- .../test_async_client_deprecation_warnings.py | 265 ++++++++++++ 72 files changed, 1441 insertions(+), 256 deletions(-) create mode 100644 pyinjective/client/indexer/__init__.py create mode 100644 pyinjective/client/indexer/grpc/__init__.py create mode 100644 pyinjective/client/indexer/grpc/indexer_grpc_account_api.py create mode 100644 pyinjective/client/indexer/grpc_stream/__init__.py create mode 100644 pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py create mode 100644 pyinjective/utils/grpc_api_request_assistant.py create mode 100644 tests/client/indexer/__init__.py create mode 100644 tests/client/indexer/configurable_account_query_servicer.py create mode 100644 tests/client/indexer/grpc/__init__.py create mode 100644 tests/client/indexer/grpc/test_indexer_grpc_account_api.py create mode 100644 tests/client/indexer/stream_grpc/__init__.py create mode 100644 tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py create mode 100644 tests/test_async_client_deprecation_warnings.py diff --git a/examples/chain_client/0_LocalOrderHash.py b/examples/chain_client/0_LocalOrderHash.py index e25d695c..5015fbad 100644 --- a/examples/chain_client/0_LocalOrderHash.py +++ b/examples/chain_client/0_LocalOrderHash.py @@ -20,7 +20,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) subaccount_id_2 = address.get_subaccount_id(index=1) diff --git a/examples/chain_client/13_MsgIncreasePositionMargin.py b/examples/chain_client/13_MsgIncreasePositionMargin.py index a2556f78..772a607f 100644 --- a/examples/chain_client/13_MsgIncreasePositionMargin.py +++ b/examples/chain_client/13_MsgIncreasePositionMargin.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/chain_client/15_MsgWithdraw.py b/examples/chain_client/15_MsgWithdraw.py index 437d0037..e48ee668 100644 --- a/examples/chain_client/15_MsgWithdraw.py +++ b/examples/chain_client/15_MsgWithdraw.py @@ -33,7 +33,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare tx msg diff --git a/examples/chain_client/16_MsgSubaccountTransfer.py b/examples/chain_client/16_MsgSubaccountTransfer.py index bc2e5baf..a9719ce5 100644 --- a/examples/chain_client/16_MsgSubaccountTransfer.py +++ b/examples/chain_client/16_MsgSubaccountTransfer.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) dest_subaccount_id = address.get_subaccount_id(index=1) diff --git a/examples/chain_client/17_MsgBatchUpdateOrders.py b/examples/chain_client/17_MsgBatchUpdateOrders.py index 3b3e7f77..64f5bded 100644 --- a/examples/chain_client/17_MsgBatchUpdateOrders.py +++ b/examples/chain_client/17_MsgBatchUpdateOrders.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/chain_client/18_MsgBid.py b/examples/chain_client/18_MsgBid.py index 5a23f172..ad4df010 100644 --- a/examples/chain_client/18_MsgBid.py +++ b/examples/chain_client/18_MsgBid.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg msg = composer.MsgBid(sender=address.to_acc_bech32(), round=16250, bid_amount=1) diff --git a/examples/chain_client/19_MsgGrant.py b/examples/chain_client/19_MsgGrant.py index 548af1a5..8c93a9f7 100644 --- a/examples/chain_client/19_MsgGrant.py +++ b/examples/chain_client/19_MsgGrant.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # subaccount_id = address.get_subaccount_id(index=0) # market_ids = ["0x0511ddc4e6586f3bfe1acb2dd905f8b8a82c97e1edaef654b12ca7e6031ca0fa"] diff --git a/examples/chain_client/1_MsgSend.py b/examples/chain_client/1_MsgSend.py index 1d3c28a6..ec1a29cf 100644 --- a/examples/chain_client/1_MsgSend.py +++ b/examples/chain_client/1_MsgSend.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg msg = composer.MsgSend( diff --git a/examples/chain_client/20_MsgExec.py b/examples/chain_client/20_MsgExec.py index c940523c..84790203 100644 --- a/examples/chain_client/20_MsgExec.py +++ b/examples/chain_client/20_MsgExec.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("5d386fbdbf11f1141010f81a46b40f94887367562bd33b452bbaa6ce1cd1381e") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" diff --git a/examples/chain_client/21_MsgRevoke.py b/examples/chain_client/21_MsgRevoke.py index 7c040810..a046aace 100644 --- a/examples/chain_client/21_MsgRevoke.py +++ b/examples/chain_client/21_MsgRevoke.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg msg = composer.MsgRevoke( diff --git a/examples/chain_client/22_MsgSendToEth.py b/examples/chain_client/22_MsgSendToEth.py index 96f6e435..71348b20 100644 --- a/examples/chain_client/22_MsgSendToEth.py +++ b/examples/chain_client/22_MsgSendToEth.py @@ -21,7 +21,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare msg asset = "injective-protocol" diff --git a/examples/chain_client/23_MsgRelayPriceFeedPrice.py b/examples/chain_client/23_MsgRelayPriceFeedPrice.py index f55c8c72..008e9e9a 100644 --- a/examples/chain_client/23_MsgRelayPriceFeedPrice.py +++ b/examples/chain_client/23_MsgRelayPriceFeedPrice.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) price = 100 price_to_send = [str(int(price * 10**18))] diff --git a/examples/chain_client/24_MsgRewardsOptOut.py b/examples/chain_client/24_MsgRewardsOptOut.py index abf63829..81f2f60b 100644 --- a/examples/chain_client/24_MsgRewardsOptOut.py +++ b/examples/chain_client/24_MsgRewardsOptOut.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg msg = composer.MsgRewardsOptOut(sender=address.to_acc_bech32()) diff --git a/examples/chain_client/25_MsgDelegate.py b/examples/chain_client/25_MsgDelegate.py index 9243b6d4..b16e1c2d 100644 --- a/examples/chain_client/25_MsgDelegate.py +++ b/examples/chain_client/25_MsgDelegate.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg validator_address = "injvaloper1ultw9r29l8nxy5u6thcgusjn95vsy2caw722q5" diff --git a/examples/chain_client/26_MsgWithdrawDelegatorReward.py b/examples/chain_client/26_MsgWithdrawDelegatorReward.py index 61b9fbef..c8c4f8a4 100644 --- a/examples/chain_client/26_MsgWithdrawDelegatorReward.py +++ b/examples/chain_client/26_MsgWithdrawDelegatorReward.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg validator_address = "injvaloper1ultw9r29l8nxy5u6thcgusjn95vsy2caw722q5" diff --git a/examples/chain_client/28_BankBalances.py b/examples/chain_client/28_BankBalances.py index 4e6ec895..6ead7b13 100644 --- a/examples/chain_client/28_BankBalances.py +++ b/examples/chain_client/28_BankBalances.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) address = "inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r" - all_bank_balances = await client.get_bank_balances(address=address) + all_bank_balances = await client.fetch_bank_balances(address=address) print(all_bank_balances) diff --git a/examples/chain_client/29_BankBalance.py b/examples/chain_client/29_BankBalance.py index 959d1c72..af17dfac 100644 --- a/examples/chain_client/29_BankBalance.py +++ b/examples/chain_client/29_BankBalance.py @@ -9,7 +9,7 @@ async def main() -> None: client = AsyncClient(network) address = "inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r" denom = "inj" - bank_balance = await client.get_bank_balance(address=address, denom=denom) + bank_balance = await client.fetch_bank_balance(address=address, denom=denom) print(bank_balance) diff --git a/examples/chain_client/2_MsgDeposit.py b/examples/chain_client/2_MsgDeposit.py index 13febc04..26d78ff3 100644 --- a/examples/chain_client/2_MsgDeposit.py +++ b/examples/chain_client/2_MsgDeposit.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare tx msg diff --git a/examples/chain_client/30_ExternalTransfer.py b/examples/chain_client/30_ExternalTransfer.py index c5dbcc53..de656150 100644 --- a/examples/chain_client/30_ExternalTransfer.py +++ b/examples/chain_client/30_ExternalTransfer.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) dest_subaccount_id = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000" diff --git a/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py b/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py index fb2a9d24..d68457aa 100644 --- a/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py +++ b/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py @@ -20,7 +20,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py b/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py index 2408b865..8fe799e9 100644 --- a/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py +++ b/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py b/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py index 9daaf419..7c7a3f64 100644 --- a/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py +++ b/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py b/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py index da3966e0..c0b4aacd 100644 --- a/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py +++ b/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare trade info market_id = "0xfafec40a7b93331c1fc89c23f66d11fbb48f38dfdd78f7f4fc4031fad90f6896" diff --git a/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py b/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py index 1116a669..35e96b4a 100644 --- a/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py +++ b/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg msg = composer.MsgInstantBinaryOptionsMarketLaunch( diff --git a/examples/chain_client/36_MsgRelayProviderPrices.py b/examples/chain_client/36_MsgRelayProviderPrices.py index 2068c572..ad12bc3a 100644 --- a/examples/chain_client/36_MsgRelayProviderPrices.py +++ b/examples/chain_client/36_MsgRelayProviderPrices.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) provider = "ufc" symbols = ["KHABIB-TKO-05/30/2023", "KHABIB-TKO-05/26/2023"] diff --git a/examples/chain_client/39_Account.py b/examples/chain_client/39_Account.py index 43692ba2..ed193834 100644 --- a/examples/chain_client/39_Account.py +++ b/examples/chain_client/39_Account.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) address = "inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr" - acc = await client.get_account(address=address) + acc = await client.fetch_account(address=address) print(acc) diff --git a/examples/chain_client/3_MsgCreateSpotLimitOrder.py b/examples/chain_client/3_MsgCreateSpotLimitOrder.py index a480e817..e392ce9b 100644 --- a/examples/chain_client/3_MsgCreateSpotLimitOrder.py +++ b/examples/chain_client/3_MsgCreateSpotLimitOrder.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/chain_client/40_MsgExecuteContract.py b/examples/chain_client/40_MsgExecuteContract.py index 3d92b825..3a349c51 100644 --- a/examples/chain_client/40_MsgExecuteContract.py +++ b/examples/chain_client/40_MsgExecuteContract.py @@ -20,7 +20,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg # NOTE: COIN MUST BE SORTED IN ALPHABETICAL ORDER BY DENOMS diff --git a/examples/chain_client/41_MsgCreateInsuranceFund.py b/examples/chain_client/41_MsgCreateInsuranceFund.py index 86f7b0b6..73a8a7ac 100644 --- a/examples/chain_client/41_MsgCreateInsuranceFund.py +++ b/examples/chain_client/41_MsgCreateInsuranceFund.py @@ -20,7 +20,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) msg = composer.MsgCreateInsuranceFund( sender=address.to_acc_bech32(), diff --git a/examples/chain_client/42_MsgUnderwrite.py b/examples/chain_client/42_MsgUnderwrite.py index 087aae33..2a8dd764 100644 --- a/examples/chain_client/42_MsgUnderwrite.py +++ b/examples/chain_client/42_MsgUnderwrite.py @@ -20,7 +20,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) msg = composer.MsgUnderwrite( sender=address.to_acc_bech32(), diff --git a/examples/chain_client/43_MsgRequestRedemption.py b/examples/chain_client/43_MsgRequestRedemption.py index 7379f055..e932b6e9 100644 --- a/examples/chain_client/43_MsgRequestRedemption.py +++ b/examples/chain_client/43_MsgRequestRedemption.py @@ -20,7 +20,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) msg = composer.MsgRequestRedemption( sender=address.to_acc_bech32(), diff --git a/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py b/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py index 4ab89394..9a584c1a 100644 --- a/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py +++ b/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py @@ -22,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg validator_address = "injvaloper1ultw9r29l8nxy5u6thcgusjn95vsy2caw722q5" diff --git a/examples/chain_client/4_MsgCreateSpotMarketOrder.py b/examples/chain_client/4_MsgCreateSpotMarketOrder.py index 293b5145..23fff9c7 100644 --- a/examples/chain_client/4_MsgCreateSpotMarketOrder.py +++ b/examples/chain_client/4_MsgCreateSpotMarketOrder.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/chain_client/5_MsgCancelSpotOrder.py b/examples/chain_client/5_MsgCancelSpotOrder.py index f94638bd..e9358b91 100644 --- a/examples/chain_client/5_MsgCancelSpotOrder.py +++ b/examples/chain_client/5_MsgCancelSpotOrder.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py b/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py index 865400b4..e8e729a6 100644 --- a/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py +++ b/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py b/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py index db69d7c1..cc1895f9 100644 --- a/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py +++ b/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/chain_client/8_MsgCancelDerivativeOrder.py b/examples/chain_client/8_MsgCancelDerivativeOrder.py index f2cda909..67abca22 100644 --- a/examples/chain_client/8_MsgCancelDerivativeOrder.py +++ b/examples/chain_client/8_MsgCancelDerivativeOrder.py @@ -19,7 +19,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info diff --git a/examples/exchange_client/accounts_rpc/1_StreamSubaccountBalance.py b/examples/exchange_client/accounts_rpc/1_StreamSubaccountBalance.py index 9a0364eb..2a9e9c23 100644 --- a/examples/exchange_client/accounts_rpc/1_StreamSubaccountBalance.py +++ b/examples/exchange_client/accounts_rpc/1_StreamSubaccountBalance.py @@ -1,18 +1,41 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def balance_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to balance updates ({exception})") + + +def stream_closed_processor(): + print("The balance updates stream has been closed") + + async def main() -> None: network = Network.testnet() client = AsyncClient(network) subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001" denoms = ["inj", "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"] - subaccount = await client.stream_subaccount_balance(subaccount_id=subaccount_id, denoms=denoms) - async for balance in subaccount: - print("Subaccount balance Update:\n") - print(balance) + task = asyncio.get_event_loop().create_task( + client.listen_subaccount_balance_updates( + subaccount_id=subaccount_id, + callback=balance_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + denoms=denoms, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/accounts_rpc/2_SubaccountBalance.py b/examples/exchange_client/accounts_rpc/2_SubaccountBalance.py index dd48b9f3..ecec1061 100644 --- a/examples/exchange_client/accounts_rpc/2_SubaccountBalance.py +++ b/examples/exchange_client/accounts_rpc/2_SubaccountBalance.py @@ -9,7 +9,7 @@ async def main() -> None: client = AsyncClient(network) subaccount_id = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000" denom = "inj" - balance = await client.get_subaccount_balance(subaccount_id=subaccount_id, denom=denom) + balance = await client.fetch_subaccount_balance(subaccount_id=subaccount_id, denom=denom) print(balance) diff --git a/examples/exchange_client/accounts_rpc/3_SubaccountsList.py b/examples/exchange_client/accounts_rpc/3_SubaccountsList.py index af523e2d..c7243509 100644 --- a/examples/exchange_client/accounts_rpc/3_SubaccountsList.py +++ b/examples/exchange_client/accounts_rpc/3_SubaccountsList.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) account_address = "inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt" - subacc_list = await client.get_subaccount_list(account_address) + subacc_list = await client.fetch_subaccounts_list(account_address) print(subacc_list) diff --git a/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py b/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py index e67d2ab6..610b0723 100644 --- a/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py +++ b/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py @@ -13,7 +13,7 @@ async def main() -> None: skip = 1 limit = 15 end_time = 1665118340224 - subacc_history = await client.get_subaccount_history( + subacc_history = await client.fetch_subaccount_history( subaccount_id=subaccount, denom=denom, transfer_types=transfer_types, skip=skip, limit=limit, end_time=end_time ) print(subacc_history) diff --git a/examples/exchange_client/accounts_rpc/6_SubaccountOrderSummary.py b/examples/exchange_client/accounts_rpc/6_SubaccountOrderSummary.py index 9026e4e1..a150cd22 100644 --- a/examples/exchange_client/accounts_rpc/6_SubaccountOrderSummary.py +++ b/examples/exchange_client/accounts_rpc/6_SubaccountOrderSummary.py @@ -10,7 +10,7 @@ async def main() -> None: subaccount = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000" order_direction = "buy" market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - subacc_order_summary = await client.get_subaccount_order_summary( + subacc_order_summary = await client.fetch_subaccount_order_summary( subaccount_id=subaccount, order_direction=order_direction, market_id=market_id ) print(subacc_order_summary) diff --git a/examples/exchange_client/accounts_rpc/7_OrderStates.py b/examples/exchange_client/accounts_rpc/7_OrderStates.py index e78dc842..038d42d5 100644 --- a/examples/exchange_client/accounts_rpc/7_OrderStates.py +++ b/examples/exchange_client/accounts_rpc/7_OrderStates.py @@ -15,7 +15,7 @@ async def main() -> None: "0x82113f3998999bdc3892feaab2c4e53ba06c5fe887a2d5f9763397240f24da50", "0xbb1f036001378cecb5fff1cc69303919985b5bf058c32f37d5aaf9b804c07a06", ] - orders = await client.get_order_states( + orders = await client.fetch_order_states( spot_order_hashes=spot_order_hashes, derivative_order_hashes=derivative_order_hashes ) print(orders) diff --git a/examples/exchange_client/accounts_rpc/8_Portfolio.py b/examples/exchange_client/accounts_rpc/8_Portfolio.py index 0a922054..90cd9565 100644 --- a/examples/exchange_client/accounts_rpc/8_Portfolio.py +++ b/examples/exchange_client/accounts_rpc/8_Portfolio.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" - portfolio = await client.get_portfolio(account_address=account_address) + portfolio = await client.fetch_portfolio(account_address=account_address) print(portfolio) diff --git a/examples/exchange_client/accounts_rpc/9_Rewards.py b/examples/exchange_client/accounts_rpc/9_Rewards.py index b3a486cf..10012c2a 100644 --- a/examples/exchange_client/accounts_rpc/9_Rewards.py +++ b/examples/exchange_client/accounts_rpc/9_Rewards.py @@ -7,12 +7,9 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) - # account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" + account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" epoch = -1 - rewards = await client.get_rewards( - # account_address=account_address, - epoch=epoch - ) + rewards = await client.fetch_rewards(account_address=account_address, epoch=epoch) print(rewards) diff --git a/poetry.lock b/poetry.lock index 33a290a5..5247b2e3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1806,13 +1806,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.4.0" +version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"}, - {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"}, + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, ] [package.dependencies] @@ -2549,13 +2549,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "web3" -version = "6.10.0" +version = "6.11.0" description = "web3.py" optional = false python-versions = ">=3.7.2" files = [ - {file = "web3-6.10.0-py3-none-any.whl", hash = "sha256:070625a0da4f0fcac090fa95186e0b865a1bbc43efb78fd2ee805f7bf9cd8986"}, - {file = "web3-6.10.0.tar.gz", hash = "sha256:ea89f8a6ee74b74c3ff21954eafe00ec914365adb904c6c374f559bc46d4a61c"}, + {file = "web3-6.11.0-py3-none-any.whl", hash = "sha256:44e79da6a4765eacf137f2f388e37aa0c1e24a93bdfb462cffe9441d1be3d509"}, + {file = "web3-6.11.0.tar.gz", hash = "sha256:050dea52ae73d787272e7ecba7249f096595938c90cce1a384c20375c6b0f720"}, ] [package.dependencies] @@ -2576,10 +2576,10 @@ typing-extensions = ">=4.0.1" websockets = ">=10.0.0" [package.extras] -dev = ["black (>=22.1.0)", "build (>=0.9.0)", "bumpversion", "eth-tester[py-evm] (==v0.9.1-b.1)", "flake8 (==3.8.3)", "flaky (>=3.7.0)", "hypothesis (>=3.31.2)", "importlib-metadata (<5.0)", "ipfshttpclient (==0.8.0a2)", "isort (>=5.11.0)", "mypy (>=1.0.0)", "py-geth (>=3.11.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.18.1)", "pytest-mock (>=1.10)", "pytest-watch (>=4.2)", "pytest-xdist (>=1.29)", "setuptools (>=38.6.0)", "sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=3.18.0)", "tqdm (>4.32)", "twine (>=1.13)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)", "when-changed (>=0.3.0)"] +dev = ["black (>=22.1.0)", "build (>=0.9.0)", "bumpversion", "eth-tester[py-evm] (==v0.9.1-b.1)", "flake8 (==3.8.3)", "flaky (>=3.7.0)", "hypothesis (>=3.31.2)", "importlib-metadata (<5.0)", "ipfshttpclient (==0.8.0a2)", "isort (>=5.11.0)", "mypy (==1.4.1)", "py-geth (>=3.11.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.18.1)", "pytest-mock (>=1.10)", "pytest-watch (>=4.2)", "pytest-xdist (>=1.29)", "setuptools (>=38.6.0)", "sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=3.18.0)", "tqdm (>4.32)", "twine (>=1.13)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)", "when-changed (>=0.3.0)"] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] ipfs = ["ipfshttpclient (==0.8.0a2)"] -linter = ["black (>=22.1.0)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (>=1.0.0)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)"] +linter = ["black (>=22.1.0)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==1.4.1)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)"] tester = ["eth-tester[py-evm] (==v0.9.1-b.1)", "py-geth (>=3.11.0)"] [[package]] diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index d666c1e7..0848f218 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -2,25 +2,33 @@ import time from copy import deepcopy from decimal import Decimal -from typing import Coroutine, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Coroutine, Dict, List, Optional, Tuple, Union +from warnings import warn import aiocron import grpc +from google.protobuf import json_format +from pyinjective import constant +from pyinjective.client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi +from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi +from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi +from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream from pyinjective.composer import Composer - -from . import constant -from .client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi -from .client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi -from .core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket -from .core.network import Network -from .core.token import Token -from .exceptions import NotFoundError -from .proto.cosmos.authz.v1beta1 import query_pb2 as authz_query, query_pb2_grpc as authz_query_grpc -from .proto.cosmos.base.abci.v1beta1 import abci_pb2 as abci_type -from .proto.cosmos.base.tendermint.v1beta1 import query_pb2 as tendermint_query, query_pb2_grpc as tendermint_query_grpc -from .proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, service_pb2_grpc as tx_service_grpc -from .proto.exchange import ( +from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket +from pyinjective.core.network import Network +from pyinjective.core.token import Token +from pyinjective.exceptions import NotFoundError +from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query, query_pb2_grpc as auth_query_grpc +from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query, query_pb2_grpc as authz_query_grpc +from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query, query_pb2_grpc as bank_query_grpc +from pyinjective.proto.cosmos.base.abci.v1beta1 import abci_pb2 as abci_type +from pyinjective.proto.cosmos.base.tendermint.v1beta1 import ( + query_pb2 as tendermint_query, + query_pb2_grpc as tendermint_query_grpc, +) +from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, service_pb2_grpc as tx_service_grpc +from pyinjective.proto.exchange import ( injective_accounts_rpc_pb2 as exchange_accounts_rpc_pb, injective_accounts_rpc_pb2_grpc as exchange_accounts_rpc_grpc, injective_auction_rpc_pb2 as auction_rpc_pb, @@ -40,8 +48,8 @@ injective_spot_exchange_rpc_pb2 as spot_exchange_rpc_pb, injective_spot_exchange_rpc_pb2_grpc as spot_exchange_rpc_grpc, ) -from .proto.injective.types.v1beta1 import account_pb2 -from .utils.logger import LoggerProvider +from pyinjective.proto.injective.types.v1beta1 import account_pb2 +from pyinjective.utils.logger import LoggerProvider DEFAULT_TIMEOUTHEIGHT_SYNC_INTERVAL = 20 # seconds DEFAULT_TIMEOUTHEIGHT = 30 # blocks @@ -72,7 +80,9 @@ def __init__( ) self.stubCosmosTendermint = tendermint_query_grpc.ServiceStub(self.chain_channel) + self.stubAuth = auth_query_grpc.QueryStub(self.chain_channel) self.stubAuthz = authz_query_grpc.QueryStub(self.chain_channel) + self.stubBank = bank_query_grpc.QueryStub(self.chain_channel) self.stubTx = tx_service_grpc.ServiceStub(self.chain_channel) self.exchange_cookie = "" @@ -117,8 +127,31 @@ def __init__( self._derivative_markets: Optional[Dict[str, DerivativeMarket]] = None self._binary_option_markets: Optional[Dict[str, BinaryOptionMarket]] = None - self.bank_api = ChainGrpcBankApi(channel=self.chain_channel) - self.auth_api = ChainGrpcAuthApi(channel=self.chain_channel) + self.bank_api = ChainGrpcBankApi( + channel=self.chain_channel, + metadata_provider=self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) + self.auth_api = ChainGrpcAuthApi( + channel=self.chain_channel, + metadata_provider=self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) + + self.exchange_account_api = IndexerGrpcAccountApi( + channel=self.exchange_channel, + metadata_provider=self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_account_stream_api = IndexerGrpcAccountStream( + channel=self.exchange_channel, + metadata_provider=self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) async def all_tokens(self) -> Dict[str, Token]: if self._tokens is None: @@ -183,19 +216,44 @@ async def get_latest_block(self) -> tendermint_query.GetLatestBlockResponse: return await self.stubCosmosTendermint.GetLatestBlock(req) async def get_account(self, address: str) -> Optional[account_pb2.EthAccount]: + """ + This method is deprecated and will be removed soon. Please use `fetch_account` instead + """ + warn("This method is deprecated. Use fetch_account instead", DeprecationWarning, stacklevel=2) + try: - # metadata = await self.network.chain_metadata( - # metadata_query_provider=self._chain_cookie_metadata_requestor - # ) - account = await self.auth_api.fetch_account(address=address) - self.number = account.account_number - self.sequence = account.sequence + metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) + account_any = ( + await self.stubAuth.Account(auth_query.QueryAccountRequest(address=address), metadata=metadata) + ).account + account = account_pb2.EthAccount() + if account_any.Is(account.DESCRIPTOR): + account_any.Unpack(account) + self.number = int(account.base_account.account_number) + self.sequence = int(account.base_account.sequence) except Exception as e: LoggerProvider().logger_for_class(logging_class=self.__class__).debug( f"error while fetching sequence and number {e}" ) return None + async def fetch_account(self, address: str) -> Optional[account_pb2.EthAccount]: + result_account = None + try: + account = await self.auth_api.fetch_account(address=address) + parsed_account = account_pb2.EthAccount() + if parsed_account.DESCRIPTOR.full_name in account["account"]["@type"]: + json_format.ParseDict(js_dict=account["account"], message=parsed_account, ignore_unknown_fields=True) + self.number = parsed_account.base_account.account_number + self.sequence = parsed_account.base_account.sequence + result_account = parsed_account + except Exception as e: + LoggerProvider().logger_for_class(logging_class=self.__class__).debug( + f"error while fetching sequence and number {e}" + ) + + return result_account + async def get_request_id_by_tx_hash(self, tx_hash: bytes) -> List[int]: tx = await self.stubTx.GetTx(tx_service.GetTxRequest(hash=tx_hash)) request_ids = [] @@ -251,9 +309,23 @@ async def get_grants(self, granter: str, grantee: str, **kwargs): ) async def get_bank_balances(self, address: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_balances` instead + """ + warn("This method is deprecated. Use fetch_bank_balances instead", DeprecationWarning, stacklevel=2) + return await self.stubBank.AllBalances(bank_query.QueryAllBalancesRequest(address=address)) + + async def fetch_bank_balances(self, address: str) -> Dict[str, Any]: return await self.bank_api.fetch_balances(account_address=address) async def get_bank_balance(self, address: str, denom: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_bank_balance` instead + """ + warn("This method is deprecated. Use fetch_bank_balance instead", DeprecationWarning, stacklevel=2) + return await self.stubBank.Balance(bank_query.QueryBalanceRequest(address=address, denom=denom)) + + async def fetch_bank_balance(self, address: str, denom: str) -> Dict[str, Any]: return await self.bank_api.fetch_balance(account_address=address, denom=denom) # Injective Exchange client methods @@ -375,26 +447,77 @@ async def get_ibc_transfers(self, **kwargs): # AccountsRPC async def stream_subaccount_balance(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_subaccount_balance_updates` instead + """ + warn( + "This method is deprecated. Use listen_subaccount_balance_updates instead", DeprecationWarning, stacklevel=2 + ) req = exchange_accounts_rpc_pb.StreamSubaccountBalanceRequest( subaccount_id=subaccount_id, denoms=kwargs.get("denoms") ) return self.stubExchangeAccount.StreamSubaccountBalance(req) + async def listen_subaccount_balance_updates( + self, + subaccount_id: str, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + denoms: Optional[List[str]] = None, + ): + await self.exchange_account_stream_api.stream_subaccount_balance( + subaccount_id=subaccount_id, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + denoms=denoms, + ) + async def get_subaccount_balance(self, subaccount_id: str, denom: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_subaccount_balance` instead + """ + warn("This method is deprecated. Use fetch_subaccount_balance instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.SubaccountBalanceEndpointRequest(subaccount_id=subaccount_id, denom=denom) return await self.stubExchangeAccount.SubaccountBalanceEndpoint(req) + async def fetch_subaccount_balance(self, subaccount_id: str, denom: str) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_subaccount_balance(subaccount_id=subaccount_id, denom=denom) + async def get_subaccount_list(self, account_address: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_subaccounts_list` instead + """ + warn("This method is deprecated. Use fetch_subaccounts_list instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.SubaccountsListRequest(account_address=account_address) return await self.stubExchangeAccount.SubaccountsList(req) + async def fetch_subaccounts_list(self, address: str) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_subaccounts_list(address=address) + async def get_subaccount_balances_list(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_subaccount_balances_list` instead + """ + warn("This method is deprecated. Use fetch_subaccount_balances_list instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.SubaccountBalancesListRequest( subaccount_id=subaccount_id, denoms=kwargs.get("denoms") ) return await self.stubExchangeAccount.SubaccountBalancesList(req) + async def fetch_subaccount_balances_list( + self, subaccount_id: str, denoms: Optional[List[str]] = None + ) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_subaccount_balances_list( + subaccount_id=subaccount_id, denoms=denoms + ) + async def get_subaccount_history(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_subaccount_history` instead + """ + warn("This method is deprecated. Use fetch_subaccount_history instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.SubaccountHistoryRequest( subaccount_id=subaccount_id, denom=kwargs.get("denom"), @@ -405,7 +528,29 @@ async def get_subaccount_history(self, subaccount_id: str, **kwargs): ) return await self.stubExchangeAccount.SubaccountHistory(req) + async def fetch_subaccount_history( + self, + subaccount_id: str, + denom: Optional[str] = None, + transfer_types: Optional[List[str]] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + end_time: Optional[int] = None, + ) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_subaccount_history( + subaccount_id=subaccount_id, + denom=denom, + transfer_types=transfer_types, + skip=skip, + limit=limit, + end_time=end_time, + ) + async def get_subaccount_order_summary(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_subaccount_order_summary` instead + """ + warn("This method is deprecated. Use fetch_subaccount_order_summary instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.SubaccountOrderSummaryRequest( subaccount_id=subaccount_id, order_direction=kwargs.get("order_direction"), @@ -413,23 +558,64 @@ async def get_subaccount_order_summary(self, subaccount_id: str, **kwargs): ) return await self.stubExchangeAccount.SubaccountOrderSummary(req) + async def fetch_subaccount_order_summary( + self, + subaccount_id: str, + market_id: Optional[str] = None, + order_direction: Optional[str] = None, + ) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_subaccount_order_summary( + subaccount_id=subaccount_id, + market_id=market_id, + order_direction=order_direction, + ) + async def get_order_states(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_order_states` instead + """ + warn("This method is deprecated. Use fetch_order_states instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.OrderStatesRequest( spot_order_hashes=kwargs.get("spot_order_hashes"), derivative_order_hashes=kwargs.get("derivative_order_hashes"), ) return await self.stubExchangeAccount.OrderStates(req) + async def fetch_order_states( + self, + spot_order_hashes: Optional[List[str]] = None, + derivative_order_hashes: Optional[List[str]] = None, + ) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_order_states( + spot_order_hashes=spot_order_hashes, + derivative_order_hashes=derivative_order_hashes, + ) + async def get_portfolio(self, account_address: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_portfolio` instead + """ + warn("This method is deprecated. Use fetch_portfolio instead", DeprecationWarning, stacklevel=2) + req = exchange_accounts_rpc_pb.PortfolioRequest(account_address=account_address) return await self.stubExchangeAccount.Portfolio(req) + async def fetch_portfolio(self, account_address: str) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_portfolio(account_address=account_address) + async def get_rewards(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_rewards` instead + """ + warn("This method is deprecated. Use fetch_rewards instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.RewardsRequest( account_address=kwargs.get("account_address"), epoch=kwargs.get("epoch") ) return await self.stubExchangeAccount.Rewards(req) + async def fetch_rewards(self, account_address: Optional[str] = None, epoch: Optional[int] = None) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_rewards(account_address=account_address, epoch=epoch) + # OracleRPC async def stream_oracle_prices(self, base_symbol: str, quote_symbol: str, oracle_type: str): diff --git a/pyinjective/client/chain/grpc/chain_grpc_auction_api.py b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py index d1fd6e89..5af660b7 100644 --- a/pyinjective/client/chain/grpc/chain_grpc_auction_api.py +++ b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any, Callable, Coroutine, Dict from grpc.aio import Channel @@ -6,60 +6,31 @@ query_pb2 as auction_query_pb, query_pb2_grpc as auction_query_grpc, ) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant class ChainGrpcAuctionApi: - def __init__(self, channel: Channel): + def __init__(self, channel: Channel, metadata_provider: Coroutine): self._stub = auction_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) async def fetch_module_params(self) -> Dict[str, Any]: request = auction_query_pb.QueryAuctionParamsRequest() - response = await self._stub.AuctionParams(request) + response = await self._execute_call(call=self._stub.AuctionParams, request=request) - module_params = { - "auction_period": response.params.auction_period, - "min_next_bid_increment_rate": response.params.min_next_bid_increment_rate, - } - - return module_params + return response async def fetch_module_state(self) -> Dict[str, Any]: request = auction_query_pb.QueryModuleStateRequest() - response = await self._stub.AuctionModuleState(request) - - if response.state.highest_bid is None: - highest_bid = { - "bidder": "", - "amount": "", - } - else: - highest_bid = { - "bidder": response.state.highest_bid.bidder, - "amount": response.state.highest_bid.amount, - } - - module_state = { - "params": { - "auction_period": response.state.params.auction_period, - "min_next_bid_increment_rate": response.state.params.min_next_bid_increment_rate, - }, - "auction_round": response.state.auction_round, - "highest_bid": highest_bid, - "auction_ending_timestamp": response.state.auction_ending_timestamp, - } + response = await self._execute_call(call=self._stub.AuctionModuleState, request=request) - return module_state + return response async def fetch_current_basket(self) -> Dict[str, Any]: request = auction_query_pb.QueryCurrentAuctionBasketRequest() - response = await self._stub.CurrentAuctionBasket(request) + response = await self._execute_call(call=self._stub.CurrentAuctionBasket, request=request) - current_basket = { - "amount_list": [{"amount": coin.amount, "denom": coin.denom} for coin in response.amount], - "auction_round": response.auctionRound, - "auction_closing_time": response.auctionClosingTime, - "highest_bidder": response.highestBidder, - "highest_bid_amount": response.highestBidAmount, - } + return response - return current_basket + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/chain/grpc/chain_grpc_auth_api.py b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py index 45039288..42f2389e 100644 --- a/pyinjective/client/chain/grpc/chain_grpc_auth_api.py +++ b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py @@ -1,38 +1,34 @@ -from typing import List, Tuple +from typing import Any, Callable, Coroutine, Dict from grpc.aio import Channel -from pyinjective.client.chain.model.account import Account -from pyinjective.client.chain.model.auth_params import AuthParams -from pyinjective.client.model.pagination import Pagination, PaginationOption +from pyinjective.client.model.pagination import PaginationOption from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query_pb, query_pb2_grpc as auth_query_grpc +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant class ChainGrpcAuthApi: - def __init__(self, channel: Channel): + def __init__(self, channel: Channel, metadata_provider: Coroutine): self._stub = auth_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) - async def fetch_module_params(self) -> AuthParams: + async def fetch_module_params(self) -> Dict[str, Any]: request = auth_query_pb.QueryParamsRequest() - response = await self._stub.Params(request) + response = await self._execute_call(call=self._stub.Params, request=request) - module_params = AuthParams.from_proto_response(response=response) + return response - return module_params - - async def fetch_account(self, address: str) -> Account: + async def fetch_account(self, address: str) -> Dict[str, Any]: request = auth_query_pb.QueryAccountRequest(address=address) - response = await self._stub.Account(request) - - account = Account.from_proto(proto_account=response.account) + response = await self._execute_call(call=self._stub.Account, request=request) - return account + return response - async def fetch_accounts(self, pagination_option: PaginationOption) -> Tuple[List[Account], PaginationOption]: + async def fetch_accounts(self, pagination_option: PaginationOption) -> Dict[str, Any]: request = auth_query_pb.QueryAccountsRequest(pagination=pagination_option.create_pagination_request()) - response = await self._stub.Accounts(request) + response = await self._execute_call(call=self._stub.Accounts, request=request) - accounts = [Account.from_proto(proto_account=proto_account) for proto_account in response.accounts] - response_pagination = Pagination.from_proto(proto_pagination=response.pagination) + return response - return accounts, response_pagination + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/chain/grpc/chain_grpc_bank_api.py b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py index 5e42917e..915aa781 100644 --- a/pyinjective/client/chain/grpc/chain_grpc_bank_api.py +++ b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py @@ -1,53 +1,39 @@ -from typing import Any, Dict +from typing import Any, Callable, Coroutine, Dict from grpc.aio import Channel from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query_pb, query_pb2_grpc as bank_query_grpc +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant class ChainGrpcBankApi: - def __init__(self, channel: Channel): + def __init__(self, channel: Channel, metadata_provider: Coroutine): self._stub = bank_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) async def fetch_module_params(self) -> Dict[str, Any]: request = bank_query_pb.QueryParamsRequest() - response = await self._stub.Params(request) + response = await self._execute_call(call=self._stub.Params, request=request) - module_params = { - "default_send_enabled": response.params.default_send_enabled, - } - - return module_params + return response async def fetch_balance(self, account_address: str, denom: str) -> Dict[str, Any]: request = bank_query_pb.QueryBalanceRequest(address=account_address, denom=denom) - response = await self._stub.Balance(request) - - bank_balance = { - "amount": response.balance.amount, - "denom": response.balance.denom, - } + response = await self._execute_call(call=self._stub.Balance, request=request) - return bank_balance + return response async def fetch_balances(self, account_address: str) -> Dict[str, Any]: request = bank_query_pb.QueryAllBalancesRequest(address=account_address) - response = await self._stub.AllBalances(request) - - bank_balances = { - "balances": [{"amount": coin.amount, "denom": coin.denom} for coin in response.balances], - "pagination": {"total": response.pagination.total}, - } + response = await self._execute_call(call=self._stub.AllBalances, request=request) - return bank_balances + return response async def fetch_total_supply(self) -> Dict[str, Any]: request = bank_query_pb.QueryTotalSupplyRequest() - response = await self._stub.TotalSupply(request) + response = await self._execute_call(call=self._stub.TotalSupply, request=request) - total_supply = { - "supply": [{"amount": coin.amount, "denom": coin.denom} for coin in response.supply], - "pagination": {"next": response.pagination.next_key, "total": response.pagination.total}, - } + return response - return total_supply + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/__init__.py b/pyinjective/client/indexer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/indexer/grpc/__init__.py b/pyinjective/client/indexer/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py new file mode 100644 index 00000000..16e7e236 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py @@ -0,0 +1,107 @@ +from typing import Any, Callable, Coroutine, Dict, List, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_accounts_rpc_pb2 as exchange_accounts_pb, + injective_accounts_rpc_pb2_grpc as exchange_accounts_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcAccountApi: + def __init__(self, channel: Channel, metadata_provider: Coroutine): + self._stub = self._stub = exchange_accounts_grpc.InjectiveAccountsRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_portfolio(self, account_address: str) -> Dict[str, Any]: + request = exchange_accounts_pb.PortfolioRequest(account_address=account_address) + response = await self._execute_call(call=self._stub.Portfolio, request=request) + + return response + + async def fetch_order_states( + self, + spot_order_hashes: Optional[List[str]] = None, + derivative_order_hashes: Optional[List[str]] = None, + ) -> Dict[str, Any]: + spot_order_hashes = spot_order_hashes or [] + derivative_order_hashes = derivative_order_hashes or [] + + request = exchange_accounts_pb.OrderStatesRequest( + spot_order_hashes=spot_order_hashes, derivative_order_hashes=derivative_order_hashes + ) + response = await self._execute_call(call=self._stub.OrderStates, request=request) + + return response + + async def fetch_subaccounts_list(self, address: str) -> Dict[str, Any]: + request = exchange_accounts_pb.SubaccountsListRequest(account_address=address) + response = await self._execute_call(call=self._stub.SubaccountsList, request=request) + + return response + + async def fetch_subaccount_balances_list( + self, subaccount_id: str, denoms: Optional[List[str]] = None + ) -> Dict[str, Any]: + request = exchange_accounts_pb.SubaccountBalancesListRequest( + subaccount_id=subaccount_id, + denoms=denoms, + ) + response = await self._execute_call(call=self._stub.SubaccountBalancesList, request=request) + + return response + + async def fetch_subaccount_balance(self, subaccount_id: str, denom: str) -> Dict[str, Any]: + request = exchange_accounts_pb.SubaccountBalanceEndpointRequest( + subaccount_id=subaccount_id, + denom=denom, + ) + response = await self._execute_call(call=self._stub.SubaccountBalanceEndpoint, request=request) + + return response + + async def fetch_subaccount_history( + self, + subaccount_id: str, + denom: Optional[str] = None, + transfer_types: Optional[List[str]] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + end_time: Optional[int] = None, + ) -> Dict[str, Any]: + request = exchange_accounts_pb.SubaccountHistoryRequest( + subaccount_id=subaccount_id, + denom=denom, + transfer_types=transfer_types, + skip=skip, + limit=limit, + end_time=end_time, + ) + response = await self._execute_call(call=self._stub.SubaccountHistory, request=request) + + return response + + async def fetch_subaccount_order_summary( + self, + subaccount_id: str, + market_id: Optional[str] = None, + order_direction: Optional[str] = None, + ) -> Dict[str, Any]: + request = exchange_accounts_pb.SubaccountOrderSummaryRequest( + subaccount_id=subaccount_id, + market_id=market_id, + order_direction=order_direction, + ) + response = await self._execute_call(call=self._stub.SubaccountOrderSummary, request=request) + + return response + + async def fetch_rewards(self, account_address: Optional[str] = None, epoch: Optional[int] = None) -> Dict[str, Any]: + request = exchange_accounts_pb.RewardsRequest(account_address=account_address, epoch=epoch) + response = await self._execute_call(call=self._stub.Rewards, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc_stream/__init__.py b/pyinjective/client/indexer/grpc_stream/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py new file mode 100644 index 00000000..1ab6f0fc --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py @@ -0,0 +1,55 @@ +import asyncio +from typing import Callable, Coroutine, List, Optional + +from google.protobuf import json_format +from grpc import RpcError +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_accounts_rpc_pb2 as exchange_accounts_pb, + injective_accounts_rpc_pb2_grpc as exchange_accounts_grpc, +) + + +class IndexerGrpcAccountStream: + def __init__(self, channel: Channel, metadata_provider: Coroutine): + self._stub = self._stub = exchange_accounts_grpc.InjectiveAccountsRPCStub(channel) + self._metadata_provider = metadata_provider + + async def stream_subaccount_balance( + self, + subaccount_id: str, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + denoms: Optional[List[str]] = None, + ): + request = exchange_accounts_pb.StreamSubaccountBalanceRequest( + subaccount_id=subaccount_id, + denoms=denoms, + ) + metadata = await self._metadata_provider + stream = self._stub.StreamSubaccountBalance(request=request, metadata=metadata) + + try: + async for balance_update in stream: + update = json_format.MessageToDict( + message=balance_update, + including_default_value_fields=True, + ) + if asyncio.iscoroutinefunction(callback): + await callback(update) + else: + callback(update) + except RpcError as ex: + if on_status_callback is not None: + if asyncio.iscoroutinefunction(on_status_callback): + await on_status_callback(ex) + else: + on_status_callback(ex) + + if on_end_callback is not None: + if asyncio.iscoroutinefunction(on_end_callback): + await on_end_callback() + else: + on_end_callback() diff --git a/pyinjective/core/broadcaster.py b/pyinjective/core/broadcaster.py index a121ef22..32ad5562 100644 --- a/pyinjective/core/broadcaster.py +++ b/pyinjective/core/broadcaster.py @@ -151,7 +151,7 @@ async def broadcast(self, messages: List[any_pb2.Any]): if self._client.timeout_height == 1: await self._client.sync_timeout_height() if self._client.number == 0: - await self._client.get_account(self._account_config.trading_injective_address) + await self._client.fetch_account(self._account_config.trading_injective_address) messages_for_transaction = self._account_config.messages_prepared_for_transaction(messages=messages) diff --git a/pyinjective/utils/grpc_api_request_assistant.py b/pyinjective/utils/grpc_api_request_assistant.py new file mode 100644 index 00000000..cef27c45 --- /dev/null +++ b/pyinjective/utils/grpc_api_request_assistant.py @@ -0,0 +1,20 @@ +from typing import Any, Callable, Coroutine, Dict + +from google.protobuf import json_format + + +class GrpcApiRequestAssistant: + def __init__(self, metadata_provider: Coroutine): + super().__init__() + self._metadata_provider = metadata_provider + + async def execute_call(self, call: Callable, request) -> Dict[str, Any]: + metadata = await self._metadata_provider + response = await call(request=request, metadata=metadata) + + result = json_format.MessageToDict( + message=response, + including_default_value_fields=True, + ) + + return result diff --git a/pyproject.toml b/pyproject.toml index 9660cf8d..319171c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ max_line_length = 120 # list of plugins and rules for them [tool.flakeheaven.plugins] -pycodestyle = ["+*", "-W503"] +pycodestyle = ["+*", "-W503", "-E731"] pyflakes = ["+*"] [tool.flakeheaven.exceptions."tests/"] diff --git a/tests/client/chain/grpc/configurable_auction_query_servicer.py b/tests/client/chain/grpc/configurable_auction_query_servicer.py index c3879d59..66d3269b 100644 --- a/tests/client/chain/grpc/configurable_auction_query_servicer.py +++ b/tests/client/chain/grpc/configurable_auction_query_servicer.py @@ -13,11 +13,13 @@ def __init__(self): self.module_states = deque() self.current_baskets = deque() - async def AuctionParams(self, request: auction_query_pb.QueryAuctionParamsRequest, context=None): + async def AuctionParams(self, request: auction_query_pb.QueryAuctionParamsRequest, context=None, metadata=None): return self.auction_params.pop() - async def AuctionModuleState(self, request: auction_query_pb.QueryModuleStateRequest, context=None): + async def AuctionModuleState(self, request: auction_query_pb.QueryModuleStateRequest, context=None, metadata=None): return self.module_states.pop() - async def CurrentAuctionBasket(self, request: auction_query_pb.QueryCurrentAuctionBasketRequest, context=None): + async def CurrentAuctionBasket( + self, request: auction_query_pb.QueryCurrentAuctionBasketRequest, context=None, metadata=None + ): return self.current_baskets.pop() diff --git a/tests/client/chain/grpc/configurable_auth_query_serciver.py b/tests/client/chain/grpc/configurable_auth_query_serciver.py index 52b7c560..af7783e2 100644 --- a/tests/client/chain/grpc/configurable_auth_query_serciver.py +++ b/tests/client/chain/grpc/configurable_auth_query_serciver.py @@ -10,11 +10,11 @@ def __init__(self): self.account_responses = deque() self.accounts_responses = deque() - async def Params(self, request: auth_query_pb.QueryParamsRequest, context=None): + async def Params(self, request: auth_query_pb.QueryParamsRequest, context=None, metadata=None): return self.auth_params.pop() - async def Account(self, request: auth_query_pb.QueryAccountRequest, context=None): + async def Account(self, request: auth_query_pb.QueryAccountRequest, context=None, metadata=None): return self.account_responses.pop() - async def Accounts(self, request: auth_query_pb.QueryAccountsRequest, context=None): + async def Accounts(self, request: auth_query_pb.QueryAccountsRequest, context=None, metadata=None): return self.accounts_responses.pop() diff --git a/tests/client/chain/grpc/configurable_bank_query_servicer.py b/tests/client/chain/grpc/configurable_bank_query_servicer.py index 0f127763..015ce2c8 100644 --- a/tests/client/chain/grpc/configurable_bank_query_servicer.py +++ b/tests/client/chain/grpc/configurable_bank_query_servicer.py @@ -11,14 +11,14 @@ def __init__(self): self.balances_responses = deque() self.total_supply_responses = deque() - async def Params(self, request: bank_query_pb.QueryParamsRequest, context=None): + async def Params(self, request: bank_query_pb.QueryParamsRequest, context=None, metadata=None): return self.bank_params.pop() - async def Balance(self, request: bank_query_pb.QueryBalanceRequest, context=None): + async def Balance(self, request: bank_query_pb.QueryBalanceRequest, context=None, metadata=None): return self.balance_responses.pop() - async def AllBalances(self, request: bank_query_pb.QueryAllBalancesRequest, context=None): + async def AllBalances(self, request: bank_query_pb.QueryAllBalancesRequest, context=None, metadata=None): return self.balances_responses.pop() - async def TotalSupply(self, request: bank_query_pb.QueryTotalSupplyRequest, context=None): + async def TotalSupply(self, request: bank_query_pb.QueryTotalSupplyRequest, context=None, metadata=None): return self.total_supply_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_auction_api.py b/tests/client/chain/grpc/test_chain_grpc_auction_api.py index 8f99f1ce..e70b7ab6 100644 --- a/tests/client/chain/grpc/test_chain_grpc_auction_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_auction_api.py @@ -29,14 +29,11 @@ async def test_fetch_module_params( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuctionApi(channel=channel) + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = auction_servicer module_params = await api.fetch_module_params() - expected_params = { - "auction_period": 604800, - "min_next_bid_increment_rate": "2500000000000000", - } + expected_params = {"params": {"auctionPeriod": "604800", "minNextBidIncrementRate": "2500000000000000"}} assert expected_params == module_params @@ -61,21 +58,23 @@ async def test_fetch_module_state( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuctionApi(channel=channel) + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = auction_servicer module_state = await api.fetch_module_state() expected_state = { - "params": { - "auction_period": 604800, - "min_next_bid_increment_rate": "2500000000000000", - }, - "auction_round": 50, - "highest_bid": { - "bidder": "inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", - "amount": "\n\003inj\022\0232347518723906280000", - }, - "auction_ending_timestamp": 1687504387, + "state": { + "auctionEndingTimestamp": "1687504387", + "auctionRound": "50", + "highestBid": { + "amount": "\n\x03inj\x12\x132347518723906280000", + "bidder": "inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + }, + "params": { + "auctionPeriod": "604800", + "minNextBidIncrementRate": "2500000000000000", + }, + } } assert expected_state == module_state @@ -96,21 +95,19 @@ async def test_fetch_module_state_when_no_highest_bid_present( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuctionApi(channel=channel) + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = auction_servicer module_state = await api.fetch_module_state() expected_state = { - "params": { - "auction_period": 604800, - "min_next_bid_increment_rate": "2500000000000000", - }, - "auction_round": 50, - "highest_bid": { - "bidder": "", - "amount": "", - }, - "auction_ending_timestamp": 1687504387, + "state": { + "auctionEndingTimestamp": "1687504387", + "auctionRound": "50", + "params": { + "auctionPeriod": "604800", + "minNextBidIncrementRate": "2500000000000000", + }, + } } assert expected_state == module_state @@ -142,16 +139,28 @@ async def test_fetch_current_basket( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuctionApi(channel=channel) + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = auction_servicer current_basket = await api.fetch_current_basket() expected_basket = { - "amount_list": [{"amount": coin.amount, "denom": coin.denom} for coin in [first_amount, second_amount]], - "auction_round": 50, - "auction_closing_time": 1687504387, - "highest_bidder": "inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", - "highest_bid_amount": "2347518723906280000", + "amount": [ + { + "amount": "15059786755", + "denom": "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + }, + { + "amount": "200000", + "denom": "peggy0xf9152067989BDc8783fF586624124C05A529A5D1", + }, + ], + "auctionClosingTime": "1687504387", + "auctionRound": "50", + "highestBidAmount": "2347518723906280000", + "highestBidder": "inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", } assert expected_basket == current_basket + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/chain/grpc/test_chain_grpc_auth_api.py b/tests/client/chain/grpc/test_chain_grpc_auth_api.py index 32e44d74..0b333f8c 100644 --- a/tests/client/chain/grpc/test_chain_grpc_auth_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_auth_api.py @@ -1,3 +1,5 @@ +import base64 + import grpc import pytest from google.protobuf import any_pb2 @@ -35,16 +37,21 @@ async def test_fetch_module_params( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuthApi(channel=channel) + api = ChainGrpcAuthApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = auth_servicer module_params = await api.fetch_module_params() - - assert params.max_memo_characters == module_params.max_memo_characters - assert params.tx_sig_limit == module_params.tx_sig_limit - assert params.tx_size_cost_per_byte == module_params.tx_size_cost_per_byte - assert params.sig_verify_cost_ed25519 == module_params.sig_verify_cost_ed25519 - assert params.sig_verify_cost_secp256k1 == module_params.sig_verify_cost_secp256k1 + expected_params = { + "params": { + "maxMemoCharacters": "256", + "sigVerifyCostEd25519": "590", + "sigVerifyCostSecp256k1": "1000", + "txSigLimit": "7", + "txSizeCostPerByte": "10", + } + } + + assert expected_params == module_params @pytest.mark.asyncio async def test_fetch_account( @@ -74,17 +81,27 @@ async def test_fetch_account( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuthApi(channel=channel) + api = ChainGrpcAuthApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = auth_servicer response_account = await api.fetch_account(address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr") - - assert f"0x{account.code_hash.hex()}" == response_account.code_hash - assert base_account.address == response_account.address - assert any_pub_key.type_url == response_account.pub_key_type_url - assert any_pub_key.value == response_account.pub_key_value - assert base_account.account_number == response_account.account_number - assert base_account.sequence == response_account.sequence + expected_account = { + "account": { + "@type": "/injective.types.v1beta1.EthAccount", + "baseAccount": { + "accountNumber": str(base_account.account_number), + "address": base_account.address, + "pubKey": { + "@type": "/injective.crypto.v1beta1.ethsecp256k1.PubKey", + "key": base64.b64encode(pub_key.key).decode(), + }, + "sequence": str(base_account.sequence), + }, + "codeHash": base64.b64encode(account.code_hash).decode(), + } + } + + assert expected_account == response_account @pytest.mark.asyncio async def test_fetch_accounts( @@ -124,7 +141,7 @@ async def test_fetch_accounts( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuthApi(channel=channel) + api = ChainGrpcAuthApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = auth_servicer pagination_option = PaginationOption( @@ -135,18 +152,23 @@ async def test_fetch_accounts( count_total=True, ) - response_accounts, response_pagination = await api.fetch_accounts(pagination_option=pagination_option) + response = await api.fetch_accounts(pagination_option=pagination_option) + response_accounts = response["accounts"] + response_pagination = response["pagination"] assert 1 == len(response_accounts) response_account = response_accounts[0] - assert f"0x{account.code_hash.hex()}" == response_account.code_hash - assert base_account.address == response_account.address - assert any_pub_key.type_url == response_account.pub_key_type_url - assert any_pub_key.value == response_account.pub_key_value - assert base_account.account_number == response_account.account_number - assert base_account.sequence == response_account.sequence + assert account.code_hash == base64.b64decode(response_account["codeHash"]) + assert base_account.address == response_account["baseAccount"]["address"] + assert any_pub_key.type_url == response_account["baseAccount"]["pubKey"]["@type"] + assert pub_key.key == base64.b64decode(response_account["baseAccount"]["pubKey"]["key"]) + assert base_account.account_number == int(response_account["baseAccount"]["accountNumber"]) + assert base_account.sequence == int(response_account["baseAccount"]["sequence"]) + + assert result_pagination.next_key == base64.b64decode(response_pagination["nextKey"]) + assert result_pagination.total == int(response_pagination["total"]) - assert f"0x{result_pagination.next_key.hex()}" == response_pagination.next - assert result_pagination.total == response_pagination.total + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/chain/grpc/test_chain_grpc_bank_api.py b/tests/client/chain/grpc/test_chain_grpc_bank_api.py index e08c15ee..8cb34e62 100644 --- a/tests/client/chain/grpc/test_chain_grpc_bank_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_bank_api.py @@ -1,3 +1,5 @@ +import base64 + import grpc import pytest @@ -26,12 +28,15 @@ async def test_fetch_module_params( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcBankApi(channel=channel) + api = ChainGrpcBankApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = bank_servicer module_params = await api.fetch_module_params() expected_params = { - "default_send_enabled": True, + "params": { + "defaultSendEnabled": True, + "sendEnabled": [], + } } assert expected_params == module_params @@ -47,7 +52,7 @@ async def test_fetch_balance( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcBankApi(channel=channel) + api = ChainGrpcBankApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = bank_servicer bank_balance = await api.fetch_balance( @@ -68,13 +73,18 @@ async def test_fetch_balance( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcBankApi(channel=channel) + api = ChainGrpcBankApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = bank_servicer bank_balance = await api.fetch_balance( account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", denom="inj" ) - expected_balance = {"denom": "inj", "amount": "988987297011197594664"} + expected_balance = { + "balance": { + "denom": "inj", + "amount": "988987297011197594664", + } + } assert expected_balance == bank_balance @@ -97,7 +107,7 @@ async def test_fetch_balances( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcBankApi(channel=channel) + api = ChainGrpcBankApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = bank_servicer bank_balances = await api.fetch_balances( @@ -105,7 +115,7 @@ async def test_fetch_balances( ) expected_balances = { "balances": [{"denom": coin.denom, "amount": coin.amount} for coin in [first_balance, second_balance]], - "pagination": {"total": 2}, + "pagination": {"nextKey": "", "total": "2"}, } assert expected_balances == bank_balances @@ -139,18 +149,20 @@ async def test_fetch_total_supply( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcBankApi(channel=channel) + api = ChainGrpcBankApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) api._stub = bank_servicer total_supply = await api.fetch_total_supply() + next_key = "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" expected_supply = { "supply": [{"denom": coin.denom, "amount": coin.amount} for coin in [first_supply, second_supply]], "pagination": { - "next": ( - "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" - ).encode(), - "total": 179, + "nextKey": base64.b64encode(next_key.encode()).decode(), + "total": "179", }, } assert expected_supply == total_supply + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/__init__.py b/tests/client/indexer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/indexer/configurable_account_query_servicer.py b/tests/client/indexer/configurable_account_query_servicer.py new file mode 100644 index 00000000..161947c2 --- /dev/null +++ b/tests/client/indexer/configurable_account_query_servicer.py @@ -0,0 +1,58 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_accounts_rpc_pb2 as exchange_accounts_pb, + injective_accounts_rpc_pb2_grpc as exchange_accounts_grpc, +) + + +class ConfigurableAccountQueryServicer(exchange_accounts_grpc.InjectiveAccountsRPCServicer): + def __init__(self): + super().__init__() + self.portfolio_responses = deque() + self.order_states_responses = deque() + self.subaccounts_list_responses = deque() + self.subaccount_balances_list_responses = deque() + self.subaccount_balance_responses = deque() + self.subaccount_history_responses = deque() + self.subaccount_order_summary_responses = deque() + self.rewards_responses = deque() + self.stream_subaccount_balance_responses = deque() + + async def Portfolio(self, request: exchange_accounts_pb.PortfolioRequest, context=None, metadata=None): + return self.portfolio_responses.pop() + + async def OrderStates(self, request: exchange_accounts_pb.OrderStatesRequest, context=None, metadata=None): + return self.order_states_responses.pop() + + async def SubaccountsList(self, request: exchange_accounts_pb.SubaccountsListRequest, context=None, metadata=None): + return self.subaccounts_list_responses.pop() + + async def SubaccountBalancesList( + self, request: exchange_accounts_pb.SubaccountBalancesListRequest, context=None, metadata=None + ): + return self.subaccount_balances_list_responses.pop() + + async def SubaccountBalanceEndpoint( + self, request: exchange_accounts_pb.SubaccountBalanceEndpointRequest, context=None, metadata=None + ): + return self.subaccount_balance_responses.pop() + + async def SubaccountHistory( + self, request: exchange_accounts_pb.SubaccountHistoryRequest, context=None, metadata=None + ): + return self.subaccount_history_responses.pop() + + async def SubaccountOrderSummary( + self, request: exchange_accounts_pb.SubaccountOrderSummaryRequest, context=None, metadata=None + ): + return self.subaccount_order_summary_responses.pop() + + async def Rewards(self, request: exchange_accounts_pb.RewardsRequest, context=None, metadata=None): + return self.rewards_responses.pop() + + async def StreamSubaccountBalance( + self, request: exchange_accounts_pb.SubaccountOrderSummaryRequest, context=None, metadata=None + ): + for event in self.stream_subaccount_balance_responses: + yield event diff --git a/tests/client/indexer/grpc/__init__.py b/tests/client/indexer/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/indexer/grpc/test_indexer_grpc_account_api.py b/tests/client/indexer/grpc/test_indexer_grpc_account_api.py new file mode 100644 index 00000000..4de05c2e --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_account_api.py @@ -0,0 +1,398 @@ +import time + +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_pb +from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer + + +@pytest.fixture +def account_servicer(): + return ConfigurableAccountQueryServicer() + + +class TestIndexerGrpcAccountApi: + @pytest.mark.asyncio + async def test_fetch_portfolio( + self, + account_servicer, + ): + subaccount_portfolio = exchange_accounts_pb.SubaccountPortfolio( + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000006", + available_balance="1", + locked_balance="2", + unrealized_pnl="3", + ) + portfolio = exchange_accounts_pb.AccountPortfolio( + portfolio_value="173706.418", + available_balance="99.8782", + locked_balance="186055.7038", + unrealized_pnl="-12449.1635", + subaccounts=[subaccount_portfolio], + ) + account_servicer.portfolio_responses.append(exchange_accounts_pb.PortfolioResponse(portfolio=portfolio)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = account_servicer + + account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" + result_portfolio = await api.fetch_portfolio(account_address=account_address) + expected_portfolio = { + "portfolio": { + "portfolioValue": portfolio.portfolio_value, + "availableBalance": portfolio.available_balance, + "lockedBalance": portfolio.locked_balance, + "unrealizedPnl": portfolio.unrealized_pnl, + "subaccounts": [ + { + "subaccountId": subaccount_portfolio.subaccount_id, + "availableBalance": subaccount_portfolio.available_balance, + "lockedBalance": subaccount_portfolio.locked_balance, + "unrealizedPnl": subaccount_portfolio.unrealized_pnl, + }, + ], + } + } + + assert expected_portfolio == result_portfolio + + @pytest.mark.asyncio + async def test_order_states( + self, + account_servicer, + ): + order_state = exchange_accounts_pb.OrderStateRecord( + order_hash="0xce0d9b701f77cd6ddfda5dd3a4fe7b2d53ba83e5d6c054fb2e9e886200b7b7bb", + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000006", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + order_type="buy_po", + order_side="buy", + state="canceled", + quantity_filled="0", + quantity_remaining="1000000000000000", + created_at=1669998526840, + updated_at=1670919410587, + ) + account_servicer.order_states_responses.append( + exchange_accounts_pb.OrderStatesResponse(spot_order_states=[order_state]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = account_servicer + + result_order_states = await api.fetch_order_states(spot_order_hashes=[order_state.order_hash]) + expected_order_states = { + "spotOrderStates": [ + { + "orderHash": order_state.order_hash, + "subaccountId": order_state.subaccount_id, + "marketId": order_state.market_id, + "orderType": order_state.order_type, + "orderSide": order_state.order_side, + "state": order_state.state, + "quantityFilled": order_state.quantity_filled, + "quantityRemaining": order_state.quantity_remaining, + "createdAt": str(order_state.created_at), + "updatedAt": str(order_state.updated_at), + } + ], + "derivativeOrderStates": [], + } + + assert result_order_states == expected_order_states + + @pytest.mark.asyncio + async def test_subaccounts_list( + self, + account_servicer, + ): + account_servicer.subaccounts_list_responses.append( + exchange_accounts_pb.SubaccountsListResponse( + subaccounts=["0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000006"] + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = account_servicer + + result_subaccounts_list = await api.fetch_subaccounts_list(address="testAddress") + expected_subaccounts_list = { + "subaccounts": ["0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000006"] + } + + assert result_subaccounts_list == expected_subaccounts_list + + @pytest.mark.asyncio + async def test_subaccount_balances_list( + self, + account_servicer, + ): + deposit = exchange_accounts_pb.SubaccountDeposit( + total_balance="20", + available_balance="10", + ) + balance = exchange_accounts_pb.SubaccountBalance( + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000", + account_address="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + denom="inj", + deposit=deposit, + ) + account_servicer.subaccount_balances_list_responses.append( + exchange_accounts_pb.SubaccountBalancesListResponse(balances=[balance]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = account_servicer + + result_subaccount_balances_list = await api.fetch_subaccount_balances_list( + subaccount_id=balance.subaccount_id, denoms=[balance.denom] + ) + expected_subaccount_balances_list = { + "balances": [ + { + "subaccountId": balance.subaccount_id, + "accountAddress": balance.account_address, + "denom": balance.denom, + "deposit": { + "totalBalance": deposit.total_balance, + "availableBalance": deposit.available_balance, + }, + }, + ] + } + + assert result_subaccount_balances_list == expected_subaccount_balances_list + + @pytest.mark.asyncio + async def test_subaccount_balance( + self, + account_servicer, + ): + deposit = exchange_accounts_pb.SubaccountDeposit( + total_balance="20", + available_balance="10", + ) + balance = exchange_accounts_pb.SubaccountBalance( + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000", + account_address="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + denom="inj", + deposit=deposit, + ) + account_servicer.subaccount_balance_responses.append( + exchange_accounts_pb.SubaccountBalanceEndpointResponse(balance=balance) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = account_servicer + + result_subaccount_balance = await api.fetch_subaccount_balance( + subaccount_id=balance.subaccount_id, + denom=balance.denom, + ) + expected_subaccount_balance = { + "balance": { + "subaccountId": balance.subaccount_id, + "accountAddress": balance.account_address, + "denom": balance.denom, + "deposit": { + "totalBalance": deposit.total_balance, + "availableBalance": deposit.available_balance, + }, + }, + } + + assert result_subaccount_balance == expected_subaccount_balance + + @pytest.mark.asyncio + async def test_subaccount_history( + self, + account_servicer, + ): + transfer = exchange_accounts_pb.SubaccountBalanceTransfer( + transfer_type="deposit", + src_account_address="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + dst_subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000", + amount=exchange_accounts_pb.CosmosCoin( + denom="inj", + amount="2000000000000000000", + ), + executed_at=1665117493543, + src_subaccount_id="", + dst_account_address="", + ) + + paging = exchange_accounts_pb.Paging( + total=5, + to=5, + count_by_subaccount=10, + ) + setattr(paging, "from", 1) + + account_servicer.subaccount_history_responses.append( + exchange_accounts_pb.SubaccountHistoryResponse(transfers=[transfer], paging=paging) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = account_servicer + + result_subaccount_history = await api.fetch_subaccount_history( + subaccount_id=transfer.dst_subaccount_id, + denom=transfer.amount.denom, + transfer_types=[transfer.transfer_type], + skip=0, + limit=5, + end_time=int(time.time() * 1e3), + ) + expected_subaccount_history = { + "transfers": [ + { + "transferType": transfer.transfer_type, + "srcAccountAddress": transfer.src_account_address, + "dstSubaccountId": transfer.dst_subaccount_id, + "amount": {"denom": transfer.amount.denom, "amount": "2000000000000000000"}, + "executedAt": str(transfer.executed_at), + "srcSubaccountId": transfer.src_subaccount_id, + "dstAccountAddress": transfer.dst_account_address, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + }, + } + + assert result_subaccount_history == expected_subaccount_history + + @pytest.mark.asyncio + async def test_subaccount_order_summary( + self, + account_servicer, + ): + account_servicer.subaccount_order_summary_responses.append( + exchange_accounts_pb.SubaccountOrderSummaryResponse(spot_orders_total=0, derivative_orders_total=20) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = account_servicer + + result_subaccount_order_summary = await api.fetch_subaccount_order_summary( + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + order_direction="buy", + ) + expected_subaccount_order_summary = {"derivativeOrdersTotal": "20", "spotOrdersTotal": "0"} + + assert result_subaccount_order_summary == expected_subaccount_order_summary + + @pytest.mark.asyncio + async def test_fetch_rewards( + self, + account_servicer, + ): + single_reward = exchange_accounts_pb.Coin( + denom="inj", + amount="2000000000000000000", + ) + + reward = exchange_accounts_pb.Reward( + account_address="inj1qra8c03h70y36j85dpvtj05juxe9z7acuvz6vg", + rewards=[single_reward], + distributed_at=1672218001897, + ) + + account_servicer.rewards_responses.append(exchange_accounts_pb.RewardsResponse(rewards=[reward])) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = account_servicer + + result_rewards = await api.fetch_rewards(account_address=reward.account_address, epoch=1) + expected_rewards = { + "rewards": [ + { + "accountAddress": reward.account_address, + "rewards": [ + { + "denom": single_reward.denom, + "amount": single_reward.amount, + } + ], + "distributedAt": str(reward.distributed_at), + } + ] + } + + assert result_rewards == expected_rewards + + @pytest.mark.asyncio + async def test_fetch_rewards( + self, + account_servicer, + ): + single_reward = exchange_accounts_pb.Coin( + denom="inj", + amount="2000000000000000000", + ) + + reward = exchange_accounts_pb.Reward( + account_address="inj1qra8c03h70y36j85dpvtj05juxe9z7acuvz6vg", + rewards=[single_reward], + distributed_at=1672218001897, + ) + + account_servicer.rewards_responses.append(exchange_accounts_pb.RewardsResponse(rewards=[reward])) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = account_servicer + + result_rewards = await api.fetch_rewards(account_address=reward.account_address, epoch=1) + expected_rewards = { + "rewards": [ + { + "accountAddress": reward.account_address, + "rewards": [ + { + "denom": single_reward.denom, + "amount": single_reward.amount, + } + ], + "distributedAt": str(reward.distributed_at), + } + ] + } + + assert result_rewards == expected_rewards + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/__init__.py b/tests/client/indexer/stream_grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py new file mode 100644 index 00000000..d1814f37 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py @@ -0,0 +1,78 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_pb +from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer + + +@pytest.fixture +def account_servicer(): + return ConfigurableAccountQueryServicer() + + +class TestIndexerGrpcAccountStream: + @pytest.mark.asyncio + async def test_fetch_portfolio( + self, + account_servicer, + ): + deposit = exchange_accounts_pb.SubaccountDeposit( + total_balance="20", + available_balance="10", + ) + balance = exchange_accounts_pb.SubaccountBalance( + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000", + account_address="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + denom="inj", + deposit=deposit, + ) + account_servicer.stream_subaccount_balance_responses.append( + exchange_accounts_pb.StreamSubaccountBalanceResponse(balance=balance, timestamp=1672218001897) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountStream(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = account_servicer + + balance_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: balance_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_subaccount_balance( + subaccount_id=balance.subaccount_id, + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + denoms=["inj"], + ) + ) + expected_balance_update = { + "balance": { + "accountAddress": balance.account_address, + "denom": balance.denom, + "deposit": { + "availableBalance": balance.deposit.available_balance, + "totalBalance": balance.deposit.total_balance, + }, + "subaccountId": balance.subaccount_id, + }, + "timestamp": "1672218001897", + } + + first_balance_update = await asyncio.wait_for(balance_updates.get(), timeout=1) + + assert first_balance_update == expected_balance_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client.py b/tests/test_async_client.py index c2bc2335..ac18c6f5 100644 --- a/tests/test_async_client.py +++ b/tests/test_async_client.py @@ -60,7 +60,7 @@ async def test_get_account_logs_exception(self, caplog): ) with caplog.at_level(logging.DEBUG): - await client.get_account(address="") + await client.fetch_account(address="") expected_log_message_prefix = "error while fetching sequence and number " found_log = next( diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py new file mode 100644 index 00000000..8e0cd485 --- /dev/null +++ b/tests/test_async_client_deprecation_warnings.py @@ -0,0 +1,265 @@ +from warnings import catch_warnings + +import pytest + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query_pb +from pyinjective.proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_pb +from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb +from tests.client.chain.grpc.configurable_auth_query_serciver import ConfigurableAuthQueryServicer +from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer +from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer + + +@pytest.fixture +def account_servicer(): + return ConfigurableAccountQueryServicer() + + +@pytest.fixture +def auth_servicer(): + return ConfigurableAuthQueryServicer() + + +@pytest.fixture +def bank_servicer(): + return ConfigurableBankQueryServicer() + + +class TestAsyncClientDeprecationWarnings: + @pytest.mark.asyncio + async def test_get_account_deprecation_warning( + self, + auth_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubAuth = auth_servicer + auth_servicer.account_responses.append(account_pb.EthAccount()) + + with catch_warnings(record=True) as all_warnings: + await client.get_account(address="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_account instead" + + @pytest.mark.asyncio + async def test_get_bank_balance_deprecation_warning( + self, + bank_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubBank = bank_servicer + bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_bank_balance(address="", denom="inj") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_bank_balance instead" + + @pytest.mark.asyncio + async def test_get_bank_balances_deprecation_warning( + self, + bank_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubBank = bank_servicer + bank_servicer.balances_responses.append(bank_query_pb.QueryAllBalancesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_bank_balances(address="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_bank_balances instead" + + @pytest.mark.asyncio + async def test_get_order_states_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubExchangeAccount = account_servicer + account_servicer.order_states_responses.append(exchange_accounts_pb.OrderStatesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_order_states(spot_order_hashes=["hash1"], derivative_order_hashes=["hash2"]) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_order_states instead" + + @pytest.mark.asyncio + async def test_get_subaccount_list_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubExchangeAccount = account_servicer + account_servicer.subaccounts_list_responses.append(exchange_accounts_pb.SubaccountsListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_subaccount_list(account_address="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_subaccounts_list instead" + + @pytest.mark.asyncio + async def test_get_subaccount_balances_list_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubExchangeAccount = account_servicer + account_servicer.subaccount_balances_list_responses.append( + exchange_accounts_pb.SubaccountBalancesListResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.get_subaccount_balances_list(subaccount_id="", denoms=[]) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_balances_list instead" + + @pytest.mark.asyncio + async def test_get_subaccount_balance_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubExchangeAccount = account_servicer + account_servicer.subaccount_balance_responses.append(exchange_accounts_pb.SubaccountBalanceEndpointResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_subaccount_balance(subaccount_id="", denom="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_balance instead" + + @pytest.mark.asyncio + async def test_get_subaccount_history_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubExchangeAccount = account_servicer + account_servicer.subaccount_history_responses.append(exchange_accounts_pb.SubaccountHistoryResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_subaccount_history(subaccount_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_history instead" + + @pytest.mark.asyncio + async def test_get_subaccount_order_summary_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubExchangeAccount = account_servicer + account_servicer.subaccount_order_summary_responses.append( + exchange_accounts_pb.SubaccountOrderSummaryResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.get_subaccount_order_summary(subaccount_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_order_summary instead" + + @pytest.mark.asyncio + async def test_get_portfolio_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubExchangeAccount = account_servicer + account_servicer.portfolio_responses.append(exchange_accounts_pb.PortfolioResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_portfolio(account_address="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_portfolio instead" + + @pytest.mark.asyncio + async def test_get_rewards_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubExchangeAccount = account_servicer + account_servicer.rewards_responses.append(exchange_accounts_pb.RewardsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_rewards(account_address="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_rewards instead" + + @pytest.mark.asyncio + async def test_stream_subaccount_balance_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubExchangeAccount = account_servicer + account_servicer.stream_subaccount_balance_responses.append( + exchange_accounts_pb.StreamSubaccountBalanceResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.stream_subaccount_balance(subaccount_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) == "This method is deprecated. Use listen_subaccount_balance_updates instead" + ) From 45a4b7e291f861b59f34d9761bd379d4d5d167e1 Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 20 Oct 2023 15:33:01 -0300 Subject: [PATCH 09/27] (feat) Implemented the AuthZ low level API component --- examples/chain_client/27_Grants.py | 2 +- pyinjective/async_client.py | 26 +++ .../client/chain/grpc/chain_grpc_authz_api.py | 62 ++++++ pyinjective/client/model/pagination.py | 10 +- ...py => configurable_auth_query_servicer.py} | 0 .../grpc/configurable_autz_query_servicer.py | 20 ++ .../chain/grpc/test_chain_grpc_authz_api.py | 202 ++++++++++++++++++ .../test_async_client_deprecation_warnings.py | 28 ++- 8 files changed, 343 insertions(+), 7 deletions(-) create mode 100644 pyinjective/client/chain/grpc/chain_grpc_authz_api.py rename tests/client/chain/grpc/{configurable_auth_query_serciver.py => configurable_auth_query_servicer.py} (100%) create mode 100644 tests/client/chain/grpc/configurable_autz_query_servicer.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_authz_api.py diff --git a/examples/chain_client/27_Grants.py b/examples/chain_client/27_Grants.py index dca8ea42..331980af 100644 --- a/examples/chain_client/27_Grants.py +++ b/examples/chain_client/27_Grants.py @@ -10,7 +10,7 @@ async def main() -> None: granter = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" grantee = "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" msg_type_url = "/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder" - authorizations = await client.get_grants(granter=granter, grantee=grantee, msg_type_url=msg_type_url) + authorizations = await client.fetch_grants(granter=granter, grantee=grantee, msg_type_url=msg_type_url) print(authorizations) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 0848f218..3e27f451 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -11,9 +11,11 @@ from pyinjective import constant from pyinjective.client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi +from pyinjective.client.chain.grpc.chain_grpc_authz_api import ChainGrpcAuthZApi from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream +from pyinjective.client.model.pagination import PaginationOption from pyinjective.composer import Composer from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket from pyinjective.core.network import Network @@ -139,6 +141,12 @@ def __init__( metadata_query_provider=self._chain_cookie_metadata_requestor ), ) + self.authz_api = ChainGrpcAuthZApi( + channel=self.chain_channel, + metadata_provider=self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) self.exchange_account_api = IndexerGrpcAccountApi( channel=self.exchange_channel, @@ -300,6 +308,10 @@ async def get_chain_id(self) -> str: return latest_block.block.header.chain_id async def get_grants(self, granter: str, grantee: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_grants` instead + """ + warn("This method is deprecated. Use fetch_grants instead", DeprecationWarning, stacklevel=2) return await self.stubAuthz.Grants( authz_query.QueryGrantsRequest( granter=granter, @@ -308,6 +320,20 @@ async def get_grants(self, granter: str, grantee: str, **kwargs): ) ) + async def fetch_grants( + self, + granter: str, + grantee: str, + msg_type_url: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.authz_api.fetch_grants( + granter=granter, + grantee=grantee, + msg_type_url=msg_type_url, + pagination=pagination, + ) + async def get_bank_balances(self, address: str): """ This method is deprecated and will be removed soon. Please use `fetch_balances` instead diff --git a/pyinjective/client/chain/grpc/chain_grpc_authz_api.py b/pyinjective/client/chain/grpc/chain_grpc_authz_api.py new file mode 100644 index 00000000..7e949bc8 --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_authz_api.py @@ -0,0 +1,62 @@ +from typing import Any, Callable, Coroutine, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query, query_pb2_grpc as authz_query_grpc +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class ChainGrpcAuthZApi: + def __init__(self, channel: Channel, metadata_provider: Coroutine): + self._stub = authz_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_grants( + self, + granter: str, + grantee: str, + msg_type_url: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = authz_query.QueryGrantsRequest( + granter=granter, grantee=grantee, msg_type_url=msg_type_url, pagination=pagination_request + ) + + response = await self._execute_call(call=self._stub.Grants, request=request) + + return response + + async def fetch_granter_grants( + self, + granter: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = authz_query.QueryGranterGrantsRequest(granter=granter, pagination=pagination_request) + + response = await self._execute_call(call=self._stub.GranterGrants, request=request) + + return response + + async def fetch_grantee_grants( + self, + grantee: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = authz_query.QueryGranteeGrantsRequest(grantee=grantee, pagination=pagination_request) + + response = await self._execute_call(call=self._stub.GranteeGrants, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/model/pagination.py b/pyinjective/client/model/pagination.py index 9a2d9ba8..ae37d6ec 100644 --- a/pyinjective/client/model/pagination.py +++ b/pyinjective/client/model/pagination.py @@ -6,11 +6,11 @@ class PaginationOption: def __init__( self, - key: Optional[str], - offset: Optional[int], - limit: Optional[int], - reverse: Optional[bool], - count_total: Optional[bool], + key: Optional[str] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + reverse: Optional[bool] = None, + count_total: Optional[bool] = None, ): super().__init__() self.key = key diff --git a/tests/client/chain/grpc/configurable_auth_query_serciver.py b/tests/client/chain/grpc/configurable_auth_query_servicer.py similarity index 100% rename from tests/client/chain/grpc/configurable_auth_query_serciver.py rename to tests/client/chain/grpc/configurable_auth_query_servicer.py diff --git a/tests/client/chain/grpc/configurable_autz_query_servicer.py b/tests/client/chain/grpc/configurable_autz_query_servicer.py new file mode 100644 index 00000000..550e3226 --- /dev/null +++ b/tests/client/chain/grpc/configurable_autz_query_servicer.py @@ -0,0 +1,20 @@ +from collections import deque + +from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query, query_pb2_grpc as authz_query_grpc + + +class ConfigurableAuthZQueryServicer(authz_query_grpc.QueryServicer): + def __init__(self): + super().__init__() + self.grants_responses = deque() + self.granter_grants_responses = deque() + self.grantee_grants_responses = deque() + + async def Grants(self, request: authz_query.QueryGrantsRequest, context=None, metadata=None): + return self.grants_responses.pop() + + async def GranterGrants(self, request: authz_query.QueryGranterGrantsRequest, context=None, metadata=None): + return self.granter_grants_responses.pop() + + async def GranteeGrants(self, request: authz_query.QueryGranteeGrantsRequest, context=None, metadata=None): + return self.grantee_grants_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_authz_api.py b/tests/client/chain/grpc/test_chain_grpc_authz_api.py new file mode 100644 index 00000000..8bcac406 --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_authz_api.py @@ -0,0 +1,202 @@ +import grpc +import pytest +from google.protobuf import any_pb2 + +from pyinjective.client.chain.grpc.chain_grpc_authz_api import ChainGrpcAuthZApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.authz.v1beta1 import authz_pb2, query_pb2 as authz_query +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from tests.client.chain.grpc.configurable_autz_query_servicer import ConfigurableAuthZQueryServicer + + +@pytest.fixture +def authz_servicer(): + return ConfigurableAuthZQueryServicer() + + +class TestChainGrpcAuthZApi: + @pytest.mark.asyncio + async def test_fetch_grants( + self, + authz_servicer, + ): + authorization = any_pb2.Any( + type_url="/injective.exchange.v1beta1.CreateSpotMarketOrderAuthz", + value=( + "\nB0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000\022" + "B0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + ).encode(), + ) + + grant = authz_pb2.Grant(authorization=authorization) + + page_response = pagination_pb.PageResponse(total=1) + + authz_servicer.grants_responses.append( + authz_query.QueryGrantsResponse( + grants=[grant], + pagination=page_response, + ) + ) + + pagination_option = PaginationOption( + offset=10, + limit=30, + reverse=False, + count_total=True, + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthZApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = authz_servicer + + result_grants = await api.fetch_grants( + granter="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + grantee="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + msg_type_url="/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder", + pagination=pagination_option, + ) + expected_grants = { + "grants": [ + { + "authorization": { + "@type": authorization.type_url, + "subaccountId": "0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000", + "marketIds": ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"], + } + } + ], + "pagination": {"nextKey": "", "total": str(page_response.total)}, + } + + assert result_grants == expected_grants + + @pytest.mark.asyncio + async def test_fetch_granter_grants( + self, + authz_servicer, + ): + authorization = any_pb2.Any( + type_url="/injective.exchange.v1beta1.CreateSpotMarketOrderAuthz", + value=( + "\nB0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000\022" + "B0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + ).encode(), + ) + + grant_authorization = authz_pb2.GrantAuthorization( + granter="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + grantee="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + authorization=authorization, + ) + + page_response = pagination_pb.PageResponse(total=1) + + authz_servicer.granter_grants_responses.append( + authz_query.QueryGranterGrantsResponse( + grants=[grant_authorization], + pagination=page_response, + ) + ) + + pagination_option = PaginationOption( + offset=10, + limit=30, + reverse=False, + count_total=True, + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthZApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = authz_servicer + + result_grants = await api.fetch_granter_grants( + granter="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + pagination=pagination_option, + ) + expected_grants = { + "grants": [ + { + "grantee": grant_authorization.grantee, + "granter": grant_authorization.granter, + "authorization": { + "@type": authorization.type_url, + "subaccountId": "0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000", + "marketIds": ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"], + }, + } + ], + "pagination": {"nextKey": "", "total": str(page_response.total)}, + } + + assert result_grants == expected_grants + + @pytest.mark.asyncio + async def test_fetch_grantee_grants( + self, + authz_servicer, + ): + authorization = any_pb2.Any( + type_url="/injective.exchange.v1beta1.CreateSpotMarketOrderAuthz", + value=( + "\nB0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000\022" + "B0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + ).encode(), + ) + + grant_authorization = authz_pb2.GrantAuthorization( + granter="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + grantee="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + authorization=authorization, + ) + + page_response = pagination_pb.PageResponse(total=1) + + authz_servicer.grantee_grants_responses.append( + authz_query.QueryGranteeGrantsResponse( + grants=[grant_authorization], + pagination=page_response, + ) + ) + + pagination_option = PaginationOption( + offset=10, + limit=30, + reverse=False, + count_total=True, + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthZApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = authz_servicer + + result_grants = await api.fetch_grantee_grants( + grantee="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + pagination=pagination_option, + ) + expected_grants = { + "grants": [ + { + "grantee": grant_authorization.grantee, + "granter": grant_authorization.granter, + "authorization": { + "@type": authorization.type_url, + "subaccountId": "0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000", + "marketIds": ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"], + }, + } + ], + "pagination": {"nextKey": "", "total": str(page_response.total)}, + } + + assert result_grants == expected_grants + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 8e0cd485..36591b5d 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -4,10 +4,12 @@ from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query_pb from pyinjective.proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_pb from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb -from tests.client.chain.grpc.configurable_auth_query_serciver import ConfigurableAuthQueryServicer +from tests.client.chain.grpc.configurable_auth_query_servicer import ConfigurableAuthQueryServicer +from tests.client.chain.grpc.configurable_autz_query_servicer import ConfigurableAuthZQueryServicer from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer @@ -22,6 +24,11 @@ def auth_servicer(): return ConfigurableAuthQueryServicer() +@pytest.fixture +def authz_servicer(): + return ConfigurableAuthZQueryServicer() + + @pytest.fixture def bank_servicer(): return ConfigurableBankQueryServicer() @@ -263,3 +270,22 @@ async def test_stream_subaccount_balance_deprecation_warning( assert ( str(all_warnings[0].message) == "This method is deprecated. Use listen_subaccount_balance_updates instead" ) + + @pytest.mark.asyncio + async def test_get_grants_deprecation_warning( + self, + authz_servicer, + ): + client = AsyncClient( + network=Network.local(), + insecure=False, + ) + client.stubAuthz = authz_servicer + authz_servicer.grants_responses.append(authz_query.QueryGrantsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_grants(granter="granter", grantee="grantee") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_grants instead" From f49d7c532677d73eec7014decbdb838fa8adfdf4 Mon Sep 17 00:00:00 2001 From: abel Date: Tue, 24 Oct 2023 12:27:15 -0300 Subject: [PATCH 10/27] (feat) Implemented low level API component for Transactions module. Added tests cases and created new functions in AsyncClient using the new component. Marked old functions as deprecated --- examples/chain_client/0_LocalOrderHash.py | 6 +- .../13_MsgIncreasePositionMargin.py | 13 +- examples/chain_client/15_MsgWithdraw.py | 13 +- .../chain_client/16_MsgSubaccountTransfer.py | 13 +- .../chain_client/17_MsgBatchUpdateOrders.py | 15 +- examples/chain_client/18_MsgBid.py | 13 +- examples/chain_client/19_MsgGrant.py | 13 +- examples/chain_client/1_MsgSend.py | 13 +- examples/chain_client/20_MsgExec.py | 21 +- examples/chain_client/21_MsgRevoke.py | 13 +- examples/chain_client/22_MsgSendToEth.py | 12 +- .../chain_client/23_MsgRelayPriceFeedPrice.py | 13 +- examples/chain_client/24_MsgRewardsOptOut.py | 13 +- examples/chain_client/25_MsgDelegate.py | 13 +- .../26_MsgWithdrawDelegatorReward.py | 13 +- examples/chain_client/2_MsgDeposit.py | 13 +- examples/chain_client/30_ExternalTransfer.py | 13 +- .../31_MsgCreateBinaryOptionsLimitOrder.py | 15 +- .../32_MsgCreateBinaryOptionsMarketOrder.py | 15 +- .../33_MsgCancelBinaryOptionsOrder.py | 15 +- .../34_MsgAdminUpdateBinaryOptionsMarket.py | 15 +- .../35_MsgInstantBinaryOptionsMarketLaunch.py | 15 +- .../chain_client/36_MsgRelayProviderPrices.py | 15 +- examples/chain_client/37_GetTx.py | 4 +- .../chain_client/3_MsgCreateSpotLimitOrder.py | 15 +- .../chain_client/40_MsgExecuteContract.py | 13 +- .../chain_client/41_MsgCreateInsuranceFund.py | 13 +- examples/chain_client/42_MsgUnderwrite.py | 13 +- .../chain_client/43_MsgRequestRedemption.py | 13 +- ...8_WithdrawValidatorCommissionAndRewards.py | 13 +- .../4_MsgCreateSpotMarketOrder.py | 15 +- examples/chain_client/5_MsgCancelSpotOrder.py | 13 +- .../6_MsgCreateDerivativeLimitOrder.py | 15 +- .../7_MsgCreateDerivativeMarketOrder.py | 15 +- .../8_MsgCancelDerivativeOrder.py | 13 +- pyinjective/async_client.py | 50 ++++- pyinjective/composer.py | 123 +++++------ pyinjective/core/broadcaster.py | 10 +- pyinjective/core/tx/__init__.py | 0 pyinjective/core/tx/grpc/__init__.py | 0 pyinjective/core/tx/grpc/tx_grpc_api.py | 33 +++ .../chain/grpc/test_chain_grpc_auth_api.py | 2 +- tests/core/tx/__init__.py | 0 tests/core/tx/grpc/__init__.py | 0 .../tx/grpc/configurable_tx_query_servicer.py | 20 ++ tests/core/tx/grpc/test_tx_grpc_api.py | 207 ++++++++++++++++++ .../test_async_client_deprecation_warnings.py | 126 +++++++++-- 47 files changed, 770 insertions(+), 269 deletions(-) create mode 100644 pyinjective/core/tx/__init__.py create mode 100644 pyinjective/core/tx/grpc/__init__.py create mode 100644 pyinjective/core/tx/grpc/tx_grpc_api.py create mode 100644 tests/core/tx/__init__.py create mode 100644 tests/core/tx/grpc/__init__.py create mode 100644 tests/core/tx/grpc/configurable_tx_query_servicer.py create mode 100644 tests/core/tx/grpc/test_tx_grpc_api.py diff --git a/examples/chain_client/0_LocalOrderHash.py b/examples/chain_client/0_LocalOrderHash.py index 5015fbad..e457bd65 100644 --- a/examples/chain_client/0_LocalOrderHash.py +++ b/examples/chain_client/0_LocalOrderHash.py @@ -112,7 +112,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) @@ -149,7 +149,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) @@ -235,7 +235,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/13_MsgIncreasePositionMargin.py b/examples/chain_client/13_MsgIncreasePositionMargin.py index 772a607f..94869aed 100644 --- a/examples/chain_client/13_MsgIncreasePositionMargin.py +++ b/examples/chain_client/13_MsgIncreasePositionMargin.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -47,14 +49,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -68,7 +71,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/15_MsgWithdraw.py b/examples/chain_client/15_MsgWithdraw.py index e48ee668..9b2aa5ed 100644 --- a/examples/chain_client/15_MsgWithdraw.py +++ b/examples/chain_client/15_MsgWithdraw.py @@ -14,6 +14,8 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -52,14 +54,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -73,7 +76,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/16_MsgSubaccountTransfer.py b/examples/chain_client/16_MsgSubaccountTransfer.py index a9719ce5..e6d349d7 100644 --- a/examples/chain_client/16_MsgSubaccountTransfer.py +++ b/examples/chain_client/16_MsgSubaccountTransfer.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -45,14 +47,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -66,7 +69,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/17_MsgBatchUpdateOrders.py b/examples/chain_client/17_MsgBatchUpdateOrders.py index 64f5bded..e1ee2210 100644 --- a/examples/chain_client/17_MsgBatchUpdateOrders.py +++ b/examples/chain_client/17_MsgBatchUpdateOrders.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -125,18 +127,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -150,7 +153,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/18_MsgBid.py b/examples/chain_client/18_MsgBid.py index ad4df010..37d17f2c 100644 --- a/examples/chain_client/18_MsgBid.py +++ b/examples/chain_client/18_MsgBid.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -37,14 +39,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -58,7 +61,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/19_MsgGrant.py b/examples/chain_client/19_MsgGrant.py index 8c93a9f7..6e56c83f 100644 --- a/examples/chain_client/19_MsgGrant.py +++ b/examples/chain_client/19_MsgGrant.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -56,14 +58,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -77,7 +80,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/1_MsgSend.py b/examples/chain_client/1_MsgSend.py index ec1a29cf..e08864d2 100644 --- a/examples/chain_client/1_MsgSend.py +++ b/examples/chain_client/1_MsgSend.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -42,14 +44,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -63,7 +66,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/20_MsgExec.py b/examples/chain_client/20_MsgExec.py index 84790203..71431c1c 100644 --- a/examples/chain_client/20_MsgExec.py +++ b/examples/chain_client/20_MsgExec.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -53,20 +55,23 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) - data = sim_res_msg[0] - unpacked_msg_res = composer.UnpackMsgExecResponse(msg_type=msg0.__class__.__name__, data=data) + sim_res_msgs = sim_res["result"]["msgResponses"] + data = sim_res_msgs[0] + unpacked_msg_res = composer.unpack_msg_exec_response( + underlying_msg_type=msg0.__class__.__name__, msg_exec_response=data + ) print("simulation msg response") print(unpacked_msg_res) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -80,7 +85,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/21_MsgRevoke.py b/examples/chain_client/21_MsgRevoke.py index a046aace..10c258b8 100644 --- a/examples/chain_client/21_MsgRevoke.py +++ b/examples/chain_client/21_MsgRevoke.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -41,14 +43,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -62,7 +65,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/22_MsgSendToEth.py b/examples/chain_client/22_MsgSendToEth.py index 71348b20..2eb48130 100644 --- a/examples/chain_client/22_MsgSendToEth.py +++ b/examples/chain_client/22_MsgSendToEth.py @@ -1,6 +1,7 @@ import asyncio import requests +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network @@ -52,14 +53,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -73,7 +75,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/23_MsgRelayPriceFeedPrice.py b/examples/chain_client/23_MsgRelayPriceFeedPrice.py index 008e9e9a..91e4e3cb 100644 --- a/examples/chain_client/23_MsgRelayPriceFeedPrice.py +++ b/examples/chain_client/23_MsgRelayPriceFeedPrice.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -42,14 +44,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -63,7 +66,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/24_MsgRewardsOptOut.py b/examples/chain_client/24_MsgRewardsOptOut.py index 81f2f60b..b9c4cb34 100644 --- a/examples/chain_client/24_MsgRewardsOptOut.py +++ b/examples/chain_client/24_MsgRewardsOptOut.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -37,14 +39,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -58,7 +61,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/25_MsgDelegate.py b/examples/chain_client/25_MsgDelegate.py index b16e1c2d..89072fd2 100644 --- a/examples/chain_client/25_MsgDelegate.py +++ b/examples/chain_client/25_MsgDelegate.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -42,14 +44,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -63,7 +66,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/26_MsgWithdrawDelegatorReward.py b/examples/chain_client/26_MsgWithdrawDelegatorReward.py index c8c4f8a4..903dd744 100644 --- a/examples/chain_client/26_MsgWithdrawDelegatorReward.py +++ b/examples/chain_client/26_MsgWithdrawDelegatorReward.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -41,14 +43,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -62,7 +65,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/2_MsgDeposit.py b/examples/chain_client/2_MsgDeposit.py index 26d78ff3..98443a48 100644 --- a/examples/chain_client/2_MsgDeposit.py +++ b/examples/chain_client/2_MsgDeposit.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -38,14 +40,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -59,7 +62,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/30_ExternalTransfer.py b/examples/chain_client/30_ExternalTransfer.py index de656150..31ecfbf0 100644 --- a/examples/chain_client/30_ExternalTransfer.py +++ b/examples/chain_client/30_ExternalTransfer.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -45,14 +47,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -66,7 +69,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py b/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py index d68457aa..93654ce6 100644 --- a/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py +++ b/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import Denom from pyinjective.core.network import Network @@ -56,18 +58,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -81,7 +84,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py b/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py index 8fe799e9..d97c8273 100644 --- a/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py +++ b/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -50,18 +52,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -75,7 +78,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py b/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py index 7c7a3f64..c059fc05 100644 --- a/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py +++ b/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -43,18 +45,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -68,7 +71,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py b/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py index c0b4aacd..c9cf8b3a 100644 --- a/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py +++ b/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -51,18 +53,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -76,7 +79,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py b/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py index 35e96b4a..a836f79e 100644 --- a/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py +++ b/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -53,18 +55,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -78,7 +81,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/36_MsgRelayProviderPrices.py b/examples/chain_client/36_MsgRelayProviderPrices.py index ad12bc3a..48ee38a7 100644 --- a/examples/chain_client/36_MsgRelayProviderPrices.py +++ b/examples/chain_client/36_MsgRelayProviderPrices.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -43,18 +45,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -68,7 +71,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/37_GetTx.py b/examples/chain_client/37_GetTx.py index ece40b65..f645dba7 100644 --- a/examples/chain_client/37_GetTx.py +++ b/examples/chain_client/37_GetTx.py @@ -7,8 +7,8 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) - tx_hash = "7746BC12EB82B4D59D036FBFF2F67BDCA6F62A20B3DBC25661707DD61D4DC1B7" - tx_logs = await client.get_tx(tx_hash=tx_hash) + tx_hash = "D265527E3171C47D01D7EC9B839A95F8F794D4E683F26F5564025961C96EFDDA" + tx_logs = await client.fetch_tx(hash=tx_hash) print(tx_logs) diff --git a/examples/chain_client/3_MsgCreateSpotLimitOrder.py b/examples/chain_client/3_MsgCreateSpotLimitOrder.py index e392ce9b..2a9315ea 100644 --- a/examples/chain_client/3_MsgCreateSpotLimitOrder.py +++ b/examples/chain_client/3_MsgCreateSpotLimitOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -51,18 +53,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -76,7 +79,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/40_MsgExecuteContract.py b/examples/chain_client/40_MsgExecuteContract.py index 3a349c51..44b7bbc1 100644 --- a/examples/chain_client/40_MsgExecuteContract.py +++ b/examples/chain_client/40_MsgExecuteContract.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -52,14 +54,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -73,7 +76,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/41_MsgCreateInsuranceFund.py b/examples/chain_client/41_MsgCreateInsuranceFund.py index 73a8a7ac..5c2b4b95 100644 --- a/examples/chain_client/41_MsgCreateInsuranceFund.py +++ b/examples/chain_client/41_MsgCreateInsuranceFund.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -46,14 +48,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -67,7 +70,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/42_MsgUnderwrite.py b/examples/chain_client/42_MsgUnderwrite.py index 2a8dd764..4232d599 100644 --- a/examples/chain_client/42_MsgUnderwrite.py +++ b/examples/chain_client/42_MsgUnderwrite.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -42,14 +44,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -63,7 +66,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/43_MsgRequestRedemption.py b/examples/chain_client/43_MsgRequestRedemption.py index e932b6e9..144c03e2 100644 --- a/examples/chain_client/43_MsgRequestRedemption.py +++ b/examples/chain_client/43_MsgRequestRedemption.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -42,14 +44,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -63,7 +66,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py b/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py index 9a584c1a..0f9e786e 100644 --- a/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py +++ b/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.composer import Composer as ProtoMsgComposer from pyinjective.core.network import Network @@ -46,14 +48,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -67,7 +70,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/4_MsgCreateSpotMarketOrder.py b/examples/chain_client/4_MsgCreateSpotMarketOrder.py index 23fff9c7..0e4a7ae1 100644 --- a/examples/chain_client/4_MsgCreateSpotMarketOrder.py +++ b/examples/chain_client/4_MsgCreateSpotMarketOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -50,18 +52,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -75,7 +78,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/5_MsgCancelSpotOrder.py b/examples/chain_client/5_MsgCancelSpotOrder.py index e9358b91..0474f87f 100644 --- a/examples/chain_client/5_MsgCancelSpotOrder.py +++ b/examples/chain_client/5_MsgCancelSpotOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -44,14 +46,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -65,7 +68,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py b/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py index e8e729a6..3e0fee5a 100644 --- a/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py +++ b/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -52,18 +54,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -77,7 +80,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py b/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py index cc1895f9..736bded5 100644 --- a/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py +++ b/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -51,18 +53,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -76,7 +79,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/8_MsgCancelDerivativeOrder.py b/examples/chain_client/8_MsgCancelDerivativeOrder.py index 67abca22..32508a3e 100644 --- a/examples/chain_client/8_MsgCancelDerivativeOrder.py +++ b/examples/chain_client/8_MsgCancelDerivativeOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.transaction import Transaction @@ -44,14 +46,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = 500000000 - gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + 20000 # add 20k for gas, fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -65,7 +68,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 3e27f451..368d0c97 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -20,6 +20,7 @@ from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket from pyinjective.core.network import Network from pyinjective.core.token import Token +from pyinjective.core.tx.grpc.tx_grpc_api import TxGrpcApi from pyinjective.exceptions import NotFoundError from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query, query_pb2_grpc as auth_query_grpc from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query, query_pb2_grpc as authz_query_grpc @@ -63,10 +64,16 @@ class AsyncClient: def __init__( self, network: Network, - insecure: bool = False, + insecure: Optional[bool] = None, credentials=grpc.ssl_channel_credentials(), ): # the `insecure` parameter is ignored and will be deprecated soon. The value is taken directly from `network` + if insecure is not None: + warn( + "insecure parameter in AsyncClient is no longer used and will be deprecated", + DeprecationWarning, + stacklevel=2, + ) self.addr = "" self.number = 0 @@ -147,6 +154,12 @@ def __init__( metadata_query_provider=self._chain_cookie_metadata_requestor ), ) + self.tx_api = TxGrpcApi( + channel=self.chain_channel, + metadata_provider=self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) self.exchange_account_api = IndexerGrpcAccountApi( channel=self.exchange_channel, @@ -198,8 +211,15 @@ def get_number(self): return self.number async def get_tx(self, tx_hash): + """ + This method is deprecated and will be removed soon. Please use `fetch_tx` instead + """ + warn("This method is deprecated. Use fetch_tx instead", DeprecationWarning, stacklevel=2) return await self.stubTx.GetTx(tx_service.GetTxRequest(hash=tx_hash)) + async def fetch_tx(self, hash: str) -> Dict[str, Any]: + return await self.tx_api.fetch_tx(hash=hash) + async def close_exchange_channel(self): await self.exchange_channel.close() self.cron.stop() @@ -278,6 +298,10 @@ async def get_request_id_by_tx_hash(self, tx_hash: bytes) -> List[int]: return request_ids async def simulate_tx(self, tx_byte: bytes) -> Tuple[Union[abci_type.SimulationResponse, grpc.RpcError], bool]: + """ + This method is deprecated and will be removed soon. Please use `simulate` instead + """ + warn("This method is deprecated. Use simulate instead", DeprecationWarning, stacklevel=2) try: req = tx_service.SimulateRequest(tx_bytes=tx_byte) metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) @@ -285,19 +309,37 @@ async def simulate_tx(self, tx_byte: bytes) -> Tuple[Union[abci_type.SimulationR except grpc.RpcError as err: return err, False + async def simulate(self, tx_bytes: bytes) -> Dict[str, Any]: + return await self.tx_api.simulate(tx_bytes=tx_bytes) + async def send_tx_sync_mode(self, tx_byte: bytes) -> abci_type.TxResponse: + """ + This method is deprecated and will be removed soon. Please use `broadcast_tx_sync_mode` instead + """ + warn("This method is deprecated. Use broadcast_tx_sync_mode instead", DeprecationWarning, stacklevel=2) req = tx_service.BroadcastTxRequest(tx_bytes=tx_byte, mode=tx_service.BroadcastMode.BROADCAST_MODE_SYNC) metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) result = await self.stubTx.BroadcastTx(request=req, metadata=metadata) return result.tx_response + async def broadcast_tx_sync_mode(self, tx_bytes: bytes) -> Dict[str, Any]: + return await self.tx_api.broadcast(tx_bytes=tx_bytes, mode=tx_service.BroadcastMode.BROADCAST_MODE_SYNC) + async def send_tx_async_mode(self, tx_byte: bytes) -> abci_type.TxResponse: + """ + This method is deprecated and will be removed soon. Please use `broadcast_tx_async_mode` instead + """ + warn("This method is deprecated. Use broadcast_tx_async_mode instead", DeprecationWarning, stacklevel=2) req = tx_service.BroadcastTxRequest(tx_bytes=tx_byte, mode=tx_service.BroadcastMode.BROADCAST_MODE_ASYNC) metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) result = await self.stubTx.BroadcastTx(request=req, metadata=metadata) return result.tx_response async def send_tx_block_mode(self, tx_byte: bytes) -> abci_type.TxResponse: + """ + This method is deprecated and will be removed soon. BLOCK broadcast mode should not be used + """ + warn("This method is deprecated. BLOCK broadcast mode should not be used", DeprecationWarning, stacklevel=2) req = tx_service.BroadcastTxRequest(tx_bytes=tx_byte, mode=tx_service.BroadcastMode.BROADCAST_MODE_BLOCK) metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) result = await self.stubTx.BroadcastTx(request=req, metadata=metadata) @@ -1082,7 +1124,7 @@ async def _initialize_tokens_and_markets(self): binary_option_markets = dict() tokens = dict() tokens_by_denom = dict() - markets_info = (await self.get_spot_markets()).markets + markets_info = (await self.get_spot_markets(market_status="active")).markets for market_info in markets_info: if "/" in market_info.ticker: @@ -1124,7 +1166,7 @@ async def _initialize_tokens_and_markets(self): spot_markets[market.id] = market - markets_info = (await self.get_derivative_markets()).markets + markets_info = (await self.get_derivative_markets(market_status="active")).markets for market_info in markets_info: quote_token_symbol = market_info.quote_token_meta.symbol @@ -1157,7 +1199,7 @@ async def _initialize_tokens_and_markets(self): derivative_markets[market.id] = market - markets_info = (await self.get_binary_options_markets()).markets + markets_info = (await self.get_binary_options_markets(market_status="active")).markets for market_info in markets_info: quote_token = tokens_by_denom.get(market_info.quote_denom, None) diff --git a/pyinjective/composer.py b/pyinjective/composer.py index 009fd1e9..b587f85c 100644 --- a/pyinjective/composer.py +++ b/pyinjective/composer.py @@ -2,30 +2,54 @@ from configparser import ConfigParser from decimal import Decimal from time import time -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional from google.protobuf import any_pb2, json_format, timestamp_pb2 from pyinjective import constant +from pyinjective.constant import ADDITIONAL_CHAIN_FORMAT_DECIMALS, INJ_DENOM +from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket +from pyinjective.core.token import Token +from pyinjective.proto.cosmos.authz.v1beta1 import authz_pb2 as cosmos_authz_pb, tx_pb2 as cosmos_authz_tx_pb +from pyinjective.proto.cosmos.bank.v1beta1 import tx_pb2 as cosmos_bank_tx_pb from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as cosmos_dot_base_dot_v1beta1_dot_coin__pb2 +from pyinjective.proto.cosmos.distribution.v1beta1 import tx_pb2 as cosmos_distribution_tx_pb +from pyinjective.proto.cosmos.gov.v1beta1 import tx_pb2 as cosmos_gov_tx_pb +from pyinjective.proto.cosmos.staking.v1beta1 import tx_pb2 as cosmos_staking_tx_pb +from pyinjective.proto.cosmwasm.wasm.v1 import tx_pb2 as wasm_tx_pb +from pyinjective.proto.injective.auction.v1beta1 import tx_pb2 as injective_auction_tx_pb from pyinjective.proto.injective.exchange.v1beta1 import ( + authz_pb2 as injective_authz_pb, exchange_pb2 as injective_dot_exchange_dot_v1beta1_dot_exchange__pb2, + tx_pb2 as injective_exchange_tx_pb, ) - -from .constant import ADDITIONAL_CHAIN_FORMAT_DECIMALS, INJ_DENOM -from .core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket -from .core.token import Token -from .proto.cosmos.authz.v1beta1 import authz_pb2 as cosmos_authz_pb, tx_pb2 as cosmos_authz_tx_pb -from .proto.cosmos.bank.v1beta1 import tx_pb2 as cosmos_bank_tx_pb -from .proto.cosmos.distribution.v1beta1 import tx_pb2 as cosmos_distribution_tx_pb -from .proto.cosmos.gov.v1beta1 import tx_pb2 as cosmos_gov_tx_pb -from .proto.cosmos.staking.v1beta1 import tx_pb2 as cosmos_staking_tx_pb -from .proto.cosmwasm.wasm.v1 import tx_pb2 as wasm_tx_pb -from .proto.injective.auction.v1beta1 import tx_pb2 as injective_auction_tx_pb -from .proto.injective.exchange.v1beta1 import authz_pb2 as injective_authz_pb, tx_pb2 as injective_exchange_tx_pb -from .proto.injective.insurance.v1beta1 import tx_pb2 as injective_insurance_tx_pb -from .proto.injective.oracle.v1beta1 import tx_pb2 as injective_oracle_tx_pb -from .proto.injective.peggy.v1 import msgs_pb2 as injective_peggy_tx_pb +from pyinjective.proto.injective.insurance.v1beta1 import tx_pb2 as injective_insurance_tx_pb +from pyinjective.proto.injective.oracle.v1beta1 import tx_pb2 as injective_oracle_tx_pb +from pyinjective.proto.injective.peggy.v1 import msgs_pb2 as injective_peggy_tx_pb + +REQUEST_TO_RESPONSE_TYPE_MAP = { + "MsgCreateSpotLimitOrder": injective_exchange_tx_pb.MsgCreateSpotLimitOrderResponse, + "MsgCreateSpotMarketOrder": injective_exchange_tx_pb.MsgCreateSpotMarketOrderResponse, + "MsgCreateDerivativeLimitOrder": injective_exchange_tx_pb.MsgCreateDerivativeLimitOrderResponse, + "MsgCreateDerivativeMarketOrder": injective_exchange_tx_pb.MsgCreateDerivativeMarketOrderResponse, + "MsgCancelSpotOrder": injective_exchange_tx_pb.MsgCancelSpotOrderResponse, + "MsgCancelDerivativeOrder": injective_exchange_tx_pb.MsgCancelDerivativeOrderResponse, + "MsgBatchCancelSpotOrders": injective_exchange_tx_pb.MsgBatchCancelSpotOrdersResponse, + "MsgBatchCancelDerivativeOrders": injective_exchange_tx_pb.MsgBatchCancelDerivativeOrdersResponse, + "MsgBatchCreateSpotLimitOrders": injective_exchange_tx_pb.MsgBatchCreateSpotLimitOrdersResponse, + "MsgBatchCreateDerivativeLimitOrders": injective_exchange_tx_pb.MsgBatchCreateDerivativeLimitOrdersResponse, + "MsgBatchUpdateOrders": injective_exchange_tx_pb.MsgBatchUpdateOrdersResponse, + "MsgDeposit": injective_exchange_tx_pb.MsgDepositResponse, + "MsgWithdraw": injective_exchange_tx_pb.MsgWithdrawResponse, + "MsgSubaccountTransfer": injective_exchange_tx_pb.MsgSubaccountTransferResponse, + "MsgLiquidatePosition": injective_exchange_tx_pb.MsgLiquidatePositionResponse, + "MsgIncreasePositionMargin": injective_exchange_tx_pb.MsgIncreasePositionMarginResponse, + "MsgCreateBinaryOptionsLimitOrder": injective_exchange_tx_pb.MsgCreateBinaryOptionsLimitOrderResponse, + "MsgCreateBinaryOptionsMarketOrder": injective_exchange_tx_pb.MsgCreateBinaryOptionsMarketOrderResponse, + "MsgCancelBinaryOptionsOrder": injective_exchange_tx_pb.MsgCancelBinaryOptionsOrderResponse, + "MsgAdminUpdateBinaryOptionsMarket": injective_exchange_tx_pb.MsgAdminUpdateBinaryOptionsMarketResponse, + "MsgInstantBinaryOptionsMarketLaunch": injective_exchange_tx_pb.MsgInstantBinaryOptionsMarketLaunchResponse, +} class Composer: @@ -930,54 +954,27 @@ def MsgResponses(response, simulation=False): @staticmethod def UnpackMsgExecResponse(msg_type, data): - # fmt: off - header_map = { - "MsgCreateSpotLimitOrder": - injective_exchange_tx_pb.MsgCreateSpotLimitOrderResponse, - "MsgCreateSpotMarketOrder": - injective_exchange_tx_pb.MsgCreateSpotMarketOrderResponse, - "MsgCreateDerivativeLimitOrder": - injective_exchange_tx_pb.MsgCreateDerivativeLimitOrderResponse, - "MsgCreateDerivativeMarketOrder": - injective_exchange_tx_pb.MsgCreateDerivativeMarketOrderResponse, - "MsgCancelSpotOrder": - injective_exchange_tx_pb.MsgCancelSpotOrderResponse, - "MsgCancelDerivativeOrder": - injective_exchange_tx_pb.MsgCancelDerivativeOrderResponse, - "MsgBatchCancelSpotOrders": - injective_exchange_tx_pb.MsgBatchCancelSpotOrdersResponse, - "MsgBatchCancelDerivativeOrders": - injective_exchange_tx_pb.MsgBatchCancelDerivativeOrdersResponse, - "MsgBatchCreateSpotLimitOrders": - injective_exchange_tx_pb.MsgBatchCreateSpotLimitOrdersResponse, - "MsgBatchCreateDerivativeLimitOrders": - injective_exchange_tx_pb.MsgBatchCreateDerivativeLimitOrdersResponse, - "MsgBatchUpdateOrders": - injective_exchange_tx_pb.MsgBatchUpdateOrdersResponse, - "MsgDeposit": - injective_exchange_tx_pb.MsgDepositResponse, - "MsgWithdraw": - injective_exchange_tx_pb.MsgWithdrawResponse, - "MsgSubaccountTransfer": - injective_exchange_tx_pb.MsgSubaccountTransferResponse, - "MsgLiquidatePosition": - injective_exchange_tx_pb.MsgLiquidatePositionResponse, - "MsgIncreasePositionMargin": - injective_exchange_tx_pb.MsgIncreasePositionMarginResponse, - "MsgCreateBinaryOptionsLimitOrder": - injective_exchange_tx_pb.MsgCreateBinaryOptionsLimitOrderResponse, - "MsgCreateBinaryOptionsMarketOrder": - injective_exchange_tx_pb.MsgCreateBinaryOptionsMarketOrderResponse, - "MsgCancelBinaryOptionsOrder": - injective_exchange_tx_pb.MsgCancelBinaryOptionsOrderResponse, - "MsgAdminUpdateBinaryOptionsMarket": - injective_exchange_tx_pb.MsgAdminUpdateBinaryOptionsMarketResponse, - "MsgInstantBinaryOptionsMarketLaunch": - injective_exchange_tx_pb.MsgInstantBinaryOptionsMarketLaunchResponse, - } - # fmt: on + responses = [] + dict_message = json_format.MessageToDict(message=data, including_default_value_fields=True) + json_responses = Composer.unpack_msg_exec_response(underlying_msg_type=msg_type, msg_exec_response=dict_message) + for json_response in json_responses: + response = REQUEST_TO_RESPONSE_TYPE_MAP[msg_type]() + json_format.ParseDict(js_dict=json_response, message=response, ignore_unknown_fields=True) + responses.append(response) + return responses + + @staticmethod + def unpack_msg_exec_response(underlying_msg_type: str, msg_exec_response: Dict[str, Any]) -> List[Dict[str, Any]]: + grpc_response = cosmos_authz_tx_pb.MsgExecResponse() + json_format.ParseDict(js_dict=msg_exec_response, message=grpc_response, ignore_unknown_fields=True) + responses = [ + json_format.MessageToDict( + message=REQUEST_TO_RESPONSE_TYPE_MAP[underlying_msg_type].FromString(result), + including_default_value_fields=True, + ) + for result in grpc_response.results + ] - responses = [header_map[msg_type].FromString(result) for result in data.results] return responses @staticmethod diff --git a/pyinjective/core/broadcaster.py b/pyinjective/core/broadcaster.py index 32ad5562..379afdaa 100644 --- a/pyinjective/core/broadcaster.py +++ b/pyinjective/core/broadcaster.py @@ -4,6 +4,7 @@ from typing import List, Optional from google.protobuf import any_pb2 +from grpc import RpcError from pyinjective import PrivateKey, PublicKey, Transaction from pyinjective.async_client import AsyncClient @@ -174,7 +175,7 @@ async def broadcast(self, messages: List[any_pb2.Any]): tx_raw_bytes = transaction.get_tx_data(sig, self._account_config.trading_public_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode - transaction_result = await self._client.send_tx_sync_mode(tx_raw_bytes) + transaction_result = await self._client.broadcast_tx_sync_mode(tx_raw_bytes) return transaction_result @@ -254,9 +255,10 @@ async def configure_gas_fee_for_transaction( sim_tx_raw_bytes = transaction.get_tx_data(sim_sig, public_key) # simulate tx - (sim_res, success) = await self._client.simulate_tx(sim_tx_raw_bytes) - if not success: - raise RuntimeError(f"Transaction simulation error: {sim_res}") + try: + sim_res = await self._client.simulate_tx(sim_tx_raw_bytes) + except RpcError as ex: + raise RuntimeError(f"Transaction simulation error: {ex}") gas_limit = math.ceil(Decimal(str(sim_res.gas_info.gas_used)) * self._gas_limit_adjustment_multiplier) diff --git a/pyinjective/core/tx/__init__.py b/pyinjective/core/tx/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/core/tx/grpc/__init__.py b/pyinjective/core/tx/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/core/tx/grpc/tx_grpc_api.py b/pyinjective/core/tx/grpc/tx_grpc_api.py new file mode 100644 index 00000000..27992359 --- /dev/null +++ b/pyinjective/core/tx/grpc/tx_grpc_api.py @@ -0,0 +1,33 @@ +from typing import Any, Callable, Coroutine, Dict + +from grpc.aio import Channel + +from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, service_pb2_grpc as tx_service_grpc +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class TxGrpcApi: + def __init__(self, channel: Channel, metadata_provider: Coroutine): + self._stub = tx_service_grpc.ServiceStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def simulate(self, tx_bytes: bytes) -> Dict[str, Any]: + request = tx_service.SimulateRequest(tx_bytes=tx_bytes) + response = await self._execute_call(call=self._stub.Simulate, request=request) + + return response + + async def fetch_tx(self, hash: str) -> Dict[str, Any]: + request = tx_service.GetTxRequest(hash=hash) + response = await self._execute_call(call=self._stub.GetTx, request=request) + + return response + + async def broadcast(self, tx_bytes: bytes, mode: int = tx_service.BROADCAST_MODE_ASYNC) -> Dict[str, Any]: + request = tx_service.BroadcastTxRequest(tx_bytes=tx_bytes, mode=mode) + response = await self._execute_call(call=self._stub.BroadcastTx, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/tests/client/chain/grpc/test_chain_grpc_auth_api.py b/tests/client/chain/grpc/test_chain_grpc_auth_api.py index 0b333f8c..abfbde1c 100644 --- a/tests/client/chain/grpc/test_chain_grpc_auth_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_auth_api.py @@ -11,7 +11,7 @@ from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb from pyinjective.proto.injective.crypto.v1beta1.ethsecp256k1 import keys_pb2 as keys_pb from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb -from tests.client.chain.grpc.configurable_auth_query_serciver import ConfigurableAuthQueryServicer +from tests.client.chain.grpc.configurable_auth_query_servicer import ConfigurableAuthQueryServicer @pytest.fixture diff --git a/tests/core/tx/__init__.py b/tests/core/tx/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/core/tx/grpc/__init__.py b/tests/core/tx/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/core/tx/grpc/configurable_tx_query_servicer.py b/tests/core/tx/grpc/configurable_tx_query_servicer.py new file mode 100644 index 00000000..db8267e5 --- /dev/null +++ b/tests/core/tx/grpc/configurable_tx_query_servicer.py @@ -0,0 +1,20 @@ +from collections import deque + +from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, service_pb2_grpc as tx_service_grpc + + +class ConfigurableTxQueryServicer(tx_service_grpc.ServiceServicer): + def __init__(self): + super().__init__() + self.simulate_responses = deque() + self.get_tx_responses = deque() + self.broadcast_responses = deque() + + async def Simulate(self, request: tx_service.SimulateRequest, context=None, metadata=None): + return self.simulate_responses.pop() + + async def GetTx(self, request: tx_service.GetTxRequest, context=None, metadata=None): + return self.get_tx_responses.pop() + + async def BroadcastTx(self, request: tx_service.BroadcastTxRequest, context=None, metadata=None): + return self.broadcast_responses.pop() diff --git a/tests/core/tx/grpc/test_tx_grpc_api.py b/tests/core/tx/grpc/test_tx_grpc_api.py new file mode 100644 index 00000000..fd04c68c --- /dev/null +++ b/tests/core/tx/grpc/test_tx_grpc_api.py @@ -0,0 +1,207 @@ +import base64 + +import grpc +import pytest +from google.protobuf import any_pb2 + +from pyinjective.core.network import Network +from pyinjective.core.tx.grpc.tx_grpc_api import TxGrpcApi +from pyinjective.proto.cosmos.base.abci.v1beta1 import abci_pb2 as abci_type +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, tx_pb2 +from pyinjective.proto.injective.crypto.v1beta1.ethsecp256k1 import keys_pb2 as keys_pb +from tests.core.tx.grpc.configurable_tx_query_servicer import ConfigurableTxQueryServicer + + +@pytest.fixture +def tx_servicer(): + return ConfigurableTxQueryServicer() + + +class TestTxGrpcApi: + @pytest.mark.asyncio + async def test_simulate( + self, + tx_servicer, + ): + gas_info = abci_type.GasInfo( + gas_wanted=130000, + gas_used=120000, + ) + simulation_result = abci_type.Result(log="Result log") + + tx_servicer.simulate_responses.append( + tx_service.SimulateResponse( + gas_info=gas_info, + result=simulation_result, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = TxGrpcApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = tx_servicer + + result_simulate = await api.simulate(tx_bytes="Transaction content".encode()) + expected_simulate = { + "gasInfo": {"gasUsed": str(gas_info.gas_used), "gasWanted": str(gas_info.gas_wanted)}, + "result": { + "data": "", + "events": [], + "log": simulation_result.log, + "msgResponses": [], + }, + } + + assert result_simulate == expected_simulate + + @pytest.mark.asyncio + async def test_get_tx( + self, + tx_servicer, + ): + tx_body = tx_pb2.TxBody( + memo="test memo", + timeout_height=17518637, + ) + + pub_key = keys_pb.PubKey(key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375") + any_pub_key = any_pb2.Any() + any_pub_key.Pack(pub_key, type_url_prefix="") + + signer_info = tx_pb2.SignerInfo( + public_key=any_pub_key, + sequence=211255, + ) + fee = tx_pb2.Fee( + amount=[coin_pb.Coin(denom="inj", amount="988987297011197594664")], + gas_limit=104757, + ) + auth_info = tx_pb2.AuthInfo( + signer_infos=[signer_info], + fee=fee, + ) + signature = ( + "\036~\024\202^t\252\346KB\377\333\266jV\030\300\353\340^\021_\227\236hc\010m\316U\314-:kK\0" + "07\337$K\275\303O\310\007\016\r\305c1\rcl\204L\323T\230\222\373\266\007/\261'" + ).encode() + transaction = tx_pb2.Tx(body=tx_body, auth_info=auth_info, signatures=[signature]) + + transaction_response = abci_type.TxResponse( + height=17518608, + txhash="D265527E3171C47D01D7EC9B839A95F8F794D4E683F26F5564025961C96EFDDA", + data=( + "126F0A252F636F736D6F732E617574687A2E763162657461312E4D736745786563526573706F6E736512460A440A42307834" + "3166303165366232666464336234633036316638343232356661656530333335366462386431376562653736313566613932" + "32663132363861666434316136" + ), + ) + + tx_servicer.get_tx_responses.append(tx_service.GetTxResponse(tx=transaction, tx_response=transaction_response)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = TxGrpcApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = tx_servicer + + result_tx = await api.fetch_tx(hash=transaction_response.txhash) + expected_tx = { + "tx": { + "authInfo": { + "fee": { + "amount": [{"amount": fee.amount[0].amount, "denom": fee.amount[0].denom}], + "gasLimit": str(fee.gas_limit), + "granter": "", + "payer": "", + }, + "signerInfos": [ + { + "publicKey": { + "@type": "/injective.crypto.v1beta1.ethsecp256k1.PubKey", + "key": base64.b64encode(pub_key.key).decode(), + }, + "sequence": str(signer_info.sequence), + } + ], + }, + "body": { + "extensionOptions": [], + "memo": tx_body.memo, + "messages": [], + "nonCriticalExtensionOptions": [], + "timeoutHeight": str(tx_body.timeout_height), + }, + "signatures": [base64.b64encode(signature).decode()], + }, + "txResponse": { + "code": 0, + "codespace": "", + "data": transaction_response.data, + "events": [], + "gasUsed": "0", + "gasWanted": "0", + "height": str(transaction_response.height), + "info": "", + "logs": [], + "rawLog": "", + "timestamp": "", + "txhash": transaction_response.txhash, + }, + } + + assert result_tx == expected_tx + + @pytest.mark.asyncio + async def test_broadcast( + self, + tx_servicer, + ): + transaction_response = abci_type.TxResponse( + height=17518608, + txhash="D265527E3171C47D01D7EC9B839A95F8F794D4E683F26F5564025961C96EFDDA", + data=( + "126F0A252F636F736D6F732E617574687A2E763162657461312E4D736745786563526573706F6E736512460A440A42307834" + "3166303165366232666464336234633036316638343232356661656530333335366462386431376562653736313566613932" + "32663132363861666434316136" + ), + ) + + tx_servicer.broadcast_responses.append( + tx_service.BroadcastTxResponse( + tx_response=transaction_response, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = TxGrpcApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api._stub = tx_servicer + + result_broadcast = await api.broadcast( + tx_bytes="Transaction content".encode(), + mode=tx_service.BroadcastMode.BROADCAST_MODE_SYNC, + ) + expected_broadcast = { + "txResponse": { + "code": 0, + "codespace": "", + "data": transaction_response.data, + "events": [], + "gasUsed": "0", + "gasWanted": "0", + "height": str(transaction_response.height), + "info": "", + "logs": [], + "rawLog": "", + "timestamp": "", + "txhash": transaction_response.txhash, + } + } + + assert result_broadcast == expected_broadcast + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 36591b5d..e347b231 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -6,12 +6,14 @@ from pyinjective.core.network import Network from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query_pb +from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service from pyinjective.proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_pb from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb from tests.client.chain.grpc.configurable_auth_query_servicer import ConfigurableAuthQueryServicer from tests.client.chain.grpc.configurable_autz_query_servicer import ConfigurableAuthZQueryServicer from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer +from tests.core.tx.grpc.configurable_tx_query_servicer import ConfigurableTxQueryServicer @pytest.fixture @@ -34,7 +36,28 @@ def bank_servicer(): return ConfigurableBankQueryServicer() +@pytest.fixture +def tx_servicer(): + return ConfigurableTxQueryServicer() + + class TestAsyncClientDeprecationWarnings: + def test_insecure_parameter_deprecation_warning( + self, + auth_servicer, + ): + with catch_warnings(record=True) as all_warnings: + AsyncClient( + network=Network.local(), + insecure=False, + ) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) == "insecure parameter in AsyncClient is no longer used and will be deprecated" + ) + @pytest.mark.asyncio async def test_get_account_deprecation_warning( self, @@ -42,7 +65,6 @@ async def test_get_account_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubAuth = auth_servicer auth_servicer.account_responses.append(account_pb.EthAccount()) @@ -61,7 +83,6 @@ async def test_get_bank_balance_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubBank = bank_servicer bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse()) @@ -80,7 +101,6 @@ async def test_get_bank_balances_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubBank = bank_servicer bank_servicer.balances_responses.append(bank_query_pb.QueryAllBalancesResponse()) @@ -99,7 +119,6 @@ async def test_get_order_states_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubExchangeAccount = account_servicer account_servicer.order_states_responses.append(exchange_accounts_pb.OrderStatesResponse()) @@ -118,7 +137,6 @@ async def test_get_subaccount_list_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubExchangeAccount = account_servicer account_servicer.subaccounts_list_responses.append(exchange_accounts_pb.SubaccountsListResponse()) @@ -137,7 +155,6 @@ async def test_get_subaccount_balances_list_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubExchangeAccount = account_servicer account_servicer.subaccount_balances_list_responses.append( @@ -158,7 +175,6 @@ async def test_get_subaccount_balance_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubExchangeAccount = account_servicer account_servicer.subaccount_balance_responses.append(exchange_accounts_pb.SubaccountBalanceEndpointResponse()) @@ -177,7 +193,6 @@ async def test_get_subaccount_history_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubExchangeAccount = account_servicer account_servicer.subaccount_history_responses.append(exchange_accounts_pb.SubaccountHistoryResponse()) @@ -196,7 +211,6 @@ async def test_get_subaccount_order_summary_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubExchangeAccount = account_servicer account_servicer.subaccount_order_summary_responses.append( @@ -217,7 +231,6 @@ async def test_get_portfolio_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubExchangeAccount = account_servicer account_servicer.portfolio_responses.append(exchange_accounts_pb.PortfolioResponse()) @@ -236,7 +249,6 @@ async def test_get_rewards_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubExchangeAccount = account_servicer account_servicer.rewards_responses.append(exchange_accounts_pb.RewardsResponse()) @@ -255,7 +267,6 @@ async def test_stream_subaccount_balance_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubExchangeAccount = account_servicer account_servicer.stream_subaccount_balance_responses.append( @@ -278,7 +289,6 @@ async def test_get_grants_deprecation_warning( ): client = AsyncClient( network=Network.local(), - insecure=False, ) client.stubAuthz = authz_servicer authz_servicer.grants_responses.append(authz_query.QueryGrantsResponse()) @@ -289,3 +299,93 @@ async def test_get_grants_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_grants instead" + + @pytest.mark.asyncio + async def test_simulate_deprecation_warning( + self, + tx_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubTx = tx_servicer + tx_servicer.simulate_responses.append(tx_service.SimulateResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.simulate_tx(tx_byte="".encode()) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use simulate instead" + + @pytest.mark.asyncio + async def test_get_tx_deprecation_warning( + self, + tx_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubTx = tx_servicer + tx_servicer.get_tx_responses.append(tx_service.GetTxResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_tx(tx_hash="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_tx instead" + + @pytest.mark.asyncio + async def test_send_tx_sync_mode_deprecation_warning( + self, + tx_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubTx = tx_servicer + tx_servicer.broadcast_responses.append(tx_service.BroadcastTxResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.send_tx_sync_mode(tx_byte="".encode()) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use broadcast_tx_sync_mode instead" + + @pytest.mark.asyncio + async def test_send_tx_async_mode_deprecation_warning( + self, + tx_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubTx = tx_servicer + tx_servicer.broadcast_responses.append(tx_service.BroadcastTxResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.send_tx_async_mode(tx_byte="".encode()) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use broadcast_tx_async_mode instead" + + @pytest.mark.asyncio + async def test_send_tx_block_mode_deprecation_warning( + self, + tx_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubTx = tx_servicer + tx_servicer.broadcast_responses.append(tx_service.BroadcastTxResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.send_tx_block_mode(tx_byte="".encode()) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. BLOCK broadcast mode should not be used" From 78612f619b379c1f5bee64719671727cce98ba07 Mon Sep 17 00:00:00 2001 From: abel Date: Tue, 24 Oct 2023 12:35:02 -0300 Subject: [PATCH 11/27] (fix) Update urllib3 requirement to solve dependabot issue --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 319171c3..9dfd7a78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ mnemonic = "*" protobuf = "*" requests = "*" safe-pysha3 = "*" -urllib3 = "<2" +urllib3 = ">=1.26.18,<2" websockets = "*" web3 = "^6.0" From 125c94bf82573508b29c85b6d31a0495dd43bb63 Mon Sep 17 00:00:00 2001 From: abel Date: Mon, 30 Oct 2023 13:20:59 -0300 Subject: [PATCH 12/27] (feat) Created Indexer Meta API components. Created new functions in AsyncClient to use them and market the ones interacting directly with the stubs as deprecated --- examples/exchange_client/meta_rpc/1_Ping.py | 2 +- .../exchange_client/meta_rpc/2_Version.py | 2 +- examples/exchange_client/meta_rpc/3_Info.py | 4 +- .../meta_rpc/4_StreamKeepAlive.py | 43 +++++--- poetry.lock | 8 +- pyinjective/async_client.py | 79 ++++++++++++--- .../chain/grpc/chain_grpc_auction_api.py | 4 +- .../client/chain/grpc/chain_grpc_auth_api.py | 4 +- .../client/chain/grpc/chain_grpc_authz_api.py | 4 +- .../client/chain/grpc/chain_grpc_bank_api.py | 4 +- .../indexer/grpc/indexer_grpc_account_api.py | 4 +- .../indexer/grpc/indexer_grpc_meta_api.py | 37 +++++++ .../indexer_grpc_account_stream.py | 6 +- .../grpc_stream/indexer_grpc_meta_stream.py | 50 ++++++++++ pyinjective/core/tx/grpc/tx_grpc_api.py | 4 +- .../utils/grpc_api_request_assistant.py | 6 +- .../chain/grpc/test_chain_grpc_auction_api.py | 8 +- .../chain/grpc/test_chain_grpc_auth_api.py | 6 +- .../chain/grpc/test_chain_grpc_authz_api.py | 6 +- .../chain/grpc/test_chain_grpc_bank_api.py | 10 +- .../configurable_meta_query_servicer.py | 28 ++++++ .../grpc/test_indexer_grpc_account_api.py | 18 ++-- .../grpc/test_indexer_grpc_meta_api.py | 99 +++++++++++++++++++ .../test_indexer_grpc_account_stream.py | 2 +- .../test_indexer_grpc_meta_stream.py | 63 ++++++++++++ tests/core/tx/grpc/test_tx_grpc_api.py | 6 +- .../test_async_client_deprecation_warnings.py | 83 +++++++++++++++- 27 files changed, 507 insertions(+), 83 deletions(-) create mode 100644 pyinjective/client/indexer/grpc/indexer_grpc_meta_api.py create mode 100644 pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py create mode 100644 tests/client/indexer/configurable_meta_query_servicer.py create mode 100644 tests/client/indexer/grpc/test_indexer_grpc_meta_api.py create mode 100644 tests/client/indexer/stream_grpc/test_indexer_grpc_meta_stream.py diff --git a/examples/exchange_client/meta_rpc/1_Ping.py b/examples/exchange_client/meta_rpc/1_Ping.py index 3b75cbae..46feca3e 100644 --- a/examples/exchange_client/meta_rpc/1_Ping.py +++ b/examples/exchange_client/meta_rpc/1_Ping.py @@ -8,7 +8,7 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - resp = await client.ping() + resp = await client.fetch_ping() print("Health OK?", resp) diff --git a/examples/exchange_client/meta_rpc/2_Version.py b/examples/exchange_client/meta_rpc/2_Version.py index 025a931b..11681dc2 100644 --- a/examples/exchange_client/meta_rpc/2_Version.py +++ b/examples/exchange_client/meta_rpc/2_Version.py @@ -8,7 +8,7 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - resp = await client.version() + resp = await client.fetch_version() print("Version:", resp) diff --git a/examples/exchange_client/meta_rpc/3_Info.py b/examples/exchange_client/meta_rpc/3_Info.py index 8f799da7..3e103e51 100644 --- a/examples/exchange_client/meta_rpc/3_Info.py +++ b/examples/exchange_client/meta_rpc/3_Info.py @@ -9,10 +9,10 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - resp = await client.info() + resp = await client.fetch_info() print("[!] Info:") print(resp) - latency = int(round(time.time() * 1000)) - resp.timestamp + latency = int(time.time() * 1000) - int(resp["timestamp"]) print(f"Server Latency: {latency}ms") diff --git a/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py b/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py index 789cabc7..7724f46e 100644 --- a/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py +++ b/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py @@ -1,22 +1,44 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to keepalive updates ({exception})") + + +def stream_closed_processor(): + print("The keepalive stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) + tasks = [] + + async def keepalive_event_processor(event: Dict[str, Any]): + print("Server announce:", event) + for task in tasks: + task.cancel() + print("Cancelled all tasks") - task1 = asyncio.create_task(get_markets(client)) - task2 = asyncio.create_task(keepalive(client, [task1])) + market_task = asyncio.get_event_loop().create_task(get_markets(client)) + tasks.append(market_task) + keepalive_task = asyncio.get_event_loop().create_task( + client.listen_keepalive( + callback=keepalive_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) try: - await asyncio.gather( - task1, - task2, - ) + await asyncio.gather(market_task, keepalive_task) except asyncio.CancelledError: print("main(): get_markets is cancelled now") @@ -27,14 +49,5 @@ async def get_markets(client): print(market) -async def keepalive(client, tasks: list): - stream = await client.stream_keepalive() - async for announce in stream: - print("Server announce:", announce) - async for task in tasks: - task.cancel() - print("Cancelled all tasks") - - if __name__ == "__main__": asyncio.get_event_loop().run_until_complete(main()) diff --git a/poetry.lock b/poetry.lock index 5247b2e3..b430ad70 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2513,13 +2513,13 @@ devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pyte [[package]] name = "urllib3" -version = "1.26.17" +version = "1.26.18" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.17-py2.py3-none-any.whl", hash = "sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b"}, - {file = "urllib3-1.26.17.tar.gz", hash = "sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21"}, + {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, + {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, ] [package.extras] @@ -2751,4 +2751,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "1c74ebf70d3cc8987fe3218c68cb29aa1a11121e1bd854af44dd2886413b5541" +content-hash = "b91b263f6ec78bab1de96a420b636b78f8804e494b7065b2ac7ab9bf7429917f" diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 368d0c97..b9de687d 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -14,7 +14,9 @@ from pyinjective.client.chain.grpc.chain_grpc_authz_api import ChainGrpcAuthZApi from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi +from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream from pyinjective.client.model.pagination import PaginationOption from pyinjective.composer import Composer from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket @@ -138,38 +140,50 @@ def __init__( self.bank_api = ChainGrpcBankApi( channel=self.chain_channel, - metadata_provider=self.network.chain_metadata( + metadata_provider=lambda: self.network.chain_metadata( metadata_query_provider=self._chain_cookie_metadata_requestor ), ) self.auth_api = ChainGrpcAuthApi( channel=self.chain_channel, - metadata_provider=self.network.chain_metadata( + metadata_provider=lambda: self.network.chain_metadata( metadata_query_provider=self._chain_cookie_metadata_requestor ), ) self.authz_api = ChainGrpcAuthZApi( channel=self.chain_channel, - metadata_provider=self.network.chain_metadata( + metadata_provider=lambda: self.network.chain_metadata( metadata_query_provider=self._chain_cookie_metadata_requestor ), ) self.tx_api = TxGrpcApi( channel=self.chain_channel, - metadata_provider=self.network.chain_metadata( + metadata_provider=lambda: self.network.chain_metadata( metadata_query_provider=self._chain_cookie_metadata_requestor ), ) self.exchange_account_api = IndexerGrpcAccountApi( channel=self.exchange_channel, - metadata_provider=self.network.exchange_metadata( + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_meta_api = IndexerGrpcMetaApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) self.exchange_account_stream_api = IndexerGrpcAccountStream( channel=self.exchange_channel, - metadata_provider=self.network.exchange_metadata( + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_meta_stream_api = IndexerGrpcMetaStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) @@ -282,16 +296,18 @@ async def fetch_account(self, address: str) -> Optional[account_pb2.EthAccount]: return result_account - async def get_request_id_by_tx_hash(self, tx_hash: bytes) -> List[int]: - tx = await self.stubTx.GetTx(tx_service.GetTxRequest(hash=tx_hash)) + async def get_request_id_by_tx_hash(self, tx_hash: str) -> List[int]: + tx = await self.tx_api.fetch_tx(hash=tx_hash) request_ids = [] - for tx in tx.tx_response.logs: - request_event = [event for event in tx.events if event.type == "request" or event.type == "report"] + for log in tx["txResponse"].get("logs", []): + request_event = [ + event for event in log.get("events", []) if event["type"] == "request" or event["type"] == "report" + ] if len(request_event) == 1: - attrs = request_event[0].attributes - attr_id = [attr for attr in attrs if attr.key == "id"] + attrs = request_event[0].get("attributes", []) + attr_id = [attr for attr in attrs if attr["key"] == "id"] if len(attr_id) == 1: - request_id = attr_id[0].value + request_id = attr_id[0]["value"] request_ids.append(int(request_id)) if len(request_ids) == 0: raise NotFoundError("Request Id is not found") @@ -415,23 +431,60 @@ async def stream_bids(self): # Meta RPC async def ping(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_ping` instead + """ + warn("This method is deprecated. Use fetch_ping instead", DeprecationWarning, stacklevel=2) req = exchange_meta_rpc_pb.PingRequest() return await self.stubMeta.Ping(req) + async def fetch_ping(self) -> Dict[str, Any]: + return await self.exchange_meta_api.fetch_ping() + async def version(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_version` instead + """ + warn("This method is deprecated. Use fetch_version instead", DeprecationWarning, stacklevel=2) req = exchange_meta_rpc_pb.VersionRequest() return await self.stubMeta.Version(req) + async def fetch_version(self) -> Dict[str, Any]: + return await self.exchange_meta_api.fetch_version() + async def info(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_info` instead + """ + warn("This method is deprecated. Use fetch_info instead", DeprecationWarning, stacklevel=2) req = exchange_meta_rpc_pb.InfoRequest( timestamp=int(time.time() * 1000), ) return await self.stubMeta.Info(req) + async def fetch_info(self) -> Dict[str, Any]: + return await self.exchange_meta_api.fetch_info() + async def stream_keepalive(self): + """ + This method is deprecated and will be removed soon. Please use `listen_keepalive` instead + """ + warn("This method is deprecated. Use listen_keepalive instead", DeprecationWarning, stacklevel=2) req = exchange_meta_rpc_pb.StreamKeepaliveRequest() return self.stubMeta.StreamKeepalive(req) + async def listen_keepalive( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_meta_stream_api.stream_keepalive( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + # Explorer RPC async def get_tx_by_hash(self, tx_hash: str): diff --git a/pyinjective/client/chain/grpc/chain_grpc_auction_api.py b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py index 5af660b7..3f9d89fb 100644 --- a/pyinjective/client/chain/grpc/chain_grpc_auction_api.py +++ b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Coroutine, Dict +from typing import Any, Callable, Dict from grpc.aio import Channel @@ -10,7 +10,7 @@ class ChainGrpcAuctionApi: - def __init__(self, channel: Channel, metadata_provider: Coroutine): + def __init__(self, channel: Channel, metadata_provider: Callable): self._stub = auction_query_grpc.QueryStub(channel) self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) diff --git a/pyinjective/client/chain/grpc/chain_grpc_auth_api.py b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py index 42f2389e..7b0c73dc 100644 --- a/pyinjective/client/chain/grpc/chain_grpc_auth_api.py +++ b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Coroutine, Dict +from typing import Any, Callable, Dict from grpc.aio import Channel @@ -8,7 +8,7 @@ class ChainGrpcAuthApi: - def __init__(self, channel: Channel, metadata_provider: Coroutine): + def __init__(self, channel: Channel, metadata_provider: Callable): self._stub = auth_query_grpc.QueryStub(channel) self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) diff --git a/pyinjective/client/chain/grpc/chain_grpc_authz_api.py b/pyinjective/client/chain/grpc/chain_grpc_authz_api.py index 7e949bc8..dae2e3c5 100644 --- a/pyinjective/client/chain/grpc/chain_grpc_authz_api.py +++ b/pyinjective/client/chain/grpc/chain_grpc_authz_api.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Coroutine, Dict, Optional +from typing import Any, Callable, Dict, Optional from grpc.aio import Channel @@ -8,7 +8,7 @@ class ChainGrpcAuthZApi: - def __init__(self, channel: Channel, metadata_provider: Coroutine): + def __init__(self, channel: Channel, metadata_provider: Callable): self._stub = authz_query_grpc.QueryStub(channel) self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) diff --git a/pyinjective/client/chain/grpc/chain_grpc_bank_api.py b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py index 915aa781..b774707f 100644 --- a/pyinjective/client/chain/grpc/chain_grpc_bank_api.py +++ b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Coroutine, Dict +from typing import Any, Callable, Dict from grpc.aio import Channel @@ -7,7 +7,7 @@ class ChainGrpcBankApi: - def __init__(self, channel: Channel, metadata_provider: Coroutine): + def __init__(self, channel: Channel, metadata_provider: Callable): self._stub = bank_query_grpc.QueryStub(channel) self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py index 16e7e236..e0296b87 100644 --- a/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py +++ b/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Coroutine, Dict, List, Optional +from typing import Any, Callable, Dict, List, Optional from grpc.aio import Channel @@ -10,7 +10,7 @@ class IndexerGrpcAccountApi: - def __init__(self, channel: Channel, metadata_provider: Coroutine): + def __init__(self, channel: Channel, metadata_provider: Callable): self._stub = self._stub = exchange_accounts_grpc.InjectiveAccountsRPCStub(channel) self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_meta_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_meta_api.py new file mode 100644 index 00000000..27e108e1 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_meta_api.py @@ -0,0 +1,37 @@ +import time +from typing import Any, Callable, Dict + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_meta_rpc_pb2 as exchange_meta_pb, + injective_meta_rpc_pb2_grpc as exchange_meta_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcMetaApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_meta_grpc.InjectiveMetaRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_ping(self) -> Dict[str, Any]: + request = exchange_meta_pb.PingRequest() + response = await self._execute_call(call=self._stub.Ping, request=request) + + return response + + async def fetch_version(self) -> Dict[str, Any]: + request = exchange_meta_pb.VersionRequest() + response = await self._execute_call(call=self._stub.Version, request=request) + + return response + + async def fetch_info(self) -> Dict[str, Any]: + request = exchange_meta_pb.InfoRequest(timestamp=int(time.time() * 1000)) + response = await self._execute_call(call=self._stub.Info, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py index 1ab6f0fc..b1bd9564 100644 --- a/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py @@ -1,5 +1,5 @@ import asyncio -from typing import Callable, Coroutine, List, Optional +from typing import Callable, List, Optional from google.protobuf import json_format from grpc import RpcError @@ -12,7 +12,7 @@ class IndexerGrpcAccountStream: - def __init__(self, channel: Channel, metadata_provider: Coroutine): + def __init__(self, channel: Channel, metadata_provider: Callable): self._stub = self._stub = exchange_accounts_grpc.InjectiveAccountsRPCStub(channel) self._metadata_provider = metadata_provider @@ -28,7 +28,7 @@ async def stream_subaccount_balance( subaccount_id=subaccount_id, denoms=denoms, ) - metadata = await self._metadata_provider + metadata = await self._metadata_provider() stream = self._stub.StreamSubaccountBalance(request=request, metadata=metadata) try: diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py new file mode 100644 index 00000000..e43c5968 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py @@ -0,0 +1,50 @@ +import asyncio +from typing import Callable, Optional + +from google.protobuf import json_format +from grpc import RpcError +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_meta_rpc_pb2 as exchange_meta_pb, + injective_meta_rpc_pb2_grpc as exchange_meta_grpc, +) + + +class IndexerGrpcMetaStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_meta_grpc.InjectiveMetaRPCStub(channel) + self._metadata_provider = metadata_provider + + async def stream_keepalive( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_meta_pb.StreamKeepaliveRequest() + metadata = await self._metadata_provider() + stream = self._stub.StreamKeepalive(request=request, metadata=metadata) + + try: + async for keepalive_update in stream: + update = json_format.MessageToDict( + message=keepalive_update, + including_default_value_fields=True, + ) + if asyncio.iscoroutinefunction(callback): + await callback(update) + else: + callback(update) + except RpcError as ex: + if on_status_callback is not None: + if asyncio.iscoroutinefunction(on_status_callback): + await on_status_callback(ex) + else: + on_status_callback(ex) + + if on_end_callback is not None: + if asyncio.iscoroutinefunction(on_end_callback): + await on_end_callback() + else: + on_end_callback() diff --git a/pyinjective/core/tx/grpc/tx_grpc_api.py b/pyinjective/core/tx/grpc/tx_grpc_api.py index 27992359..d984dbf6 100644 --- a/pyinjective/core/tx/grpc/tx_grpc_api.py +++ b/pyinjective/core/tx/grpc/tx_grpc_api.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Coroutine, Dict +from typing import Any, Callable, Dict from grpc.aio import Channel @@ -7,7 +7,7 @@ class TxGrpcApi: - def __init__(self, channel: Channel, metadata_provider: Coroutine): + def __init__(self, channel: Channel, metadata_provider: Callable): self._stub = tx_service_grpc.ServiceStub(channel) self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) diff --git a/pyinjective/utils/grpc_api_request_assistant.py b/pyinjective/utils/grpc_api_request_assistant.py index cef27c45..df88e14c 100644 --- a/pyinjective/utils/grpc_api_request_assistant.py +++ b/pyinjective/utils/grpc_api_request_assistant.py @@ -1,15 +1,15 @@ -from typing import Any, Callable, Coroutine, Dict +from typing import Any, Callable, Dict from google.protobuf import json_format class GrpcApiRequestAssistant: - def __init__(self, metadata_provider: Coroutine): + def __init__(self, metadata_provider: Callable): super().__init__() self._metadata_provider = metadata_provider async def execute_call(self, call: Callable, request) -> Dict[str, Any]: - metadata = await self._metadata_provider + metadata = await self._metadata_provider() response = await call(request=request, metadata=metadata) result = json_format.MessageToDict( diff --git a/tests/client/chain/grpc/test_chain_grpc_auction_api.py b/tests/client/chain/grpc/test_chain_grpc_auction_api.py index e70b7ab6..968ee238 100644 --- a/tests/client/chain/grpc/test_chain_grpc_auction_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_auction_api.py @@ -29,7 +29,7 @@ async def test_fetch_module_params( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuctionApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = auction_servicer module_params = await api.fetch_module_params() @@ -58,7 +58,7 @@ async def test_fetch_module_state( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuctionApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = auction_servicer module_state = await api.fetch_module_state() @@ -95,7 +95,7 @@ async def test_fetch_module_state_when_no_highest_bid_present( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuctionApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = auction_servicer module_state = await api.fetch_module_state() @@ -139,7 +139,7 @@ async def test_fetch_current_basket( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuctionApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = auction_servicer current_basket = await api.fetch_current_basket() diff --git a/tests/client/chain/grpc/test_chain_grpc_auth_api.py b/tests/client/chain/grpc/test_chain_grpc_auth_api.py index abfbde1c..24db74a4 100644 --- a/tests/client/chain/grpc/test_chain_grpc_auth_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_auth_api.py @@ -37,7 +37,7 @@ async def test_fetch_module_params( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuthApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcAuthApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = auth_servicer module_params = await api.fetch_module_params() @@ -81,7 +81,7 @@ async def test_fetch_account( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuthApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcAuthApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = auth_servicer response_account = await api.fetch_account(address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr") @@ -141,7 +141,7 @@ async def test_fetch_accounts( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuthApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcAuthApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = auth_servicer pagination_option = PaginationOption( diff --git a/tests/client/chain/grpc/test_chain_grpc_authz_api.py b/tests/client/chain/grpc/test_chain_grpc_authz_api.py index 8bcac406..b728a015 100644 --- a/tests/client/chain/grpc/test_chain_grpc_authz_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_authz_api.py @@ -50,7 +50,7 @@ async def test_fetch_grants( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuthZApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcAuthZApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = authz_servicer result_grants = await api.fetch_grants( @@ -112,7 +112,7 @@ async def test_fetch_granter_grants( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuthZApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcAuthZApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = authz_servicer result_grants = await api.fetch_granter_grants( @@ -174,7 +174,7 @@ async def test_fetch_grantee_grants( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcAuthZApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcAuthZApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = authz_servicer result_grants = await api.fetch_grantee_grants( diff --git a/tests/client/chain/grpc/test_chain_grpc_bank_api.py b/tests/client/chain/grpc/test_chain_grpc_bank_api.py index 8cb34e62..1c8c0ffa 100644 --- a/tests/client/chain/grpc/test_chain_grpc_bank_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_bank_api.py @@ -28,7 +28,7 @@ async def test_fetch_module_params( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcBankApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcBankApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = bank_servicer module_params = await api.fetch_module_params() @@ -52,7 +52,7 @@ async def test_fetch_balance( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcBankApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcBankApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = bank_servicer bank_balance = await api.fetch_balance( @@ -73,7 +73,7 @@ async def test_fetch_balance( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcBankApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcBankApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = bank_servicer bank_balance = await api.fetch_balance( @@ -107,7 +107,7 @@ async def test_fetch_balances( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcBankApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcBankApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = bank_servicer bank_balances = await api.fetch_balances( @@ -149,7 +149,7 @@ async def test_fetch_total_supply( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = ChainGrpcBankApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = ChainGrpcBankApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = bank_servicer total_supply = await api.fetch_total_supply() diff --git a/tests/client/indexer/configurable_meta_query_servicer.py b/tests/client/indexer/configurable_meta_query_servicer.py new file mode 100644 index 00000000..8c8997d0 --- /dev/null +++ b/tests/client/indexer/configurable_meta_query_servicer.py @@ -0,0 +1,28 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_meta_rpc_pb2 as exchange_meta_pb, + injective_meta_rpc_pb2_grpc as exchange_meta_grpc, +) + + +class ConfigurableMetaQueryServicer(exchange_meta_grpc.InjectiveMetaRPCServicer): + def __init__(self): + super().__init__() + self.ping_responses = deque() + self.version_responses = deque() + self.info_responses = deque() + self.stream_keepalive_responses = deque() + + async def Ping(self, request: exchange_meta_pb.PingRequest, context=None, metadata=None): + return self.ping_responses.pop() + + async def Version(self, request: exchange_meta_pb.VersionRequest, context=None, metadata=None): + return self.version_responses.pop() + + async def Info(self, request: exchange_meta_pb.InfoRequest, context=None, metadata=None): + return self.info_responses.pop() + + async def StreamKeepalive(self, request: exchange_meta_pb.StreamKeepaliveRequest, context=None, metadata=None): + for event in self.stream_keepalive_responses: + yield event diff --git a/tests/client/indexer/grpc/test_indexer_grpc_account_api.py b/tests/client/indexer/grpc/test_indexer_grpc_account_api.py index 4de05c2e..8ecbcc07 100644 --- a/tests/client/indexer/grpc/test_indexer_grpc_account_api.py +++ b/tests/client/indexer/grpc/test_indexer_grpc_account_api.py @@ -38,7 +38,7 @@ async def test_fetch_portfolio( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = account_servicer account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" @@ -86,7 +86,7 @@ async def test_order_states( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = account_servicer result_order_states = await api.fetch_order_states(spot_order_hashes=[order_state.order_hash]) @@ -124,7 +124,7 @@ async def test_subaccounts_list( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = account_servicer result_subaccounts_list = await api.fetch_subaccounts_list(address="testAddress") @@ -156,7 +156,7 @@ async def test_subaccount_balances_list( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = account_servicer result_subaccount_balances_list = await api.fetch_subaccount_balances_list( @@ -200,7 +200,7 @@ async def test_subaccount_balance( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = account_servicer result_subaccount_balance = await api.fetch_subaccount_balance( @@ -253,7 +253,7 @@ async def test_subaccount_history( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = account_servicer result_subaccount_history = await api.fetch_subaccount_history( @@ -298,7 +298,7 @@ async def test_subaccount_order_summary( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = account_servicer result_subaccount_order_summary = await api.fetch_subaccount_order_summary( @@ -331,7 +331,7 @@ async def test_fetch_rewards( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = account_servicer result_rewards = await api.fetch_rewards(account_address=reward.account_address, epoch=1) @@ -373,7 +373,7 @@ async def test_fetch_rewards( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - api = IndexerGrpcAccountApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = account_servicer result_rewards = await api.fetch_rewards(account_address=reward.account_address, epoch=1) diff --git a/tests/client/indexer/grpc/test_indexer_grpc_meta_api.py b/tests/client/indexer/grpc/test_indexer_grpc_meta_api.py new file mode 100644 index 00000000..e9fe8d94 --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_meta_api.py @@ -0,0 +1,99 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_meta_rpc_pb2 as exchange_meta_pb +from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer + + +@pytest.fixture +def meta_servicer(): + return ConfigurableMetaQueryServicer() + + +class TestIndexerGrpcMetaApi: + @pytest.mark.asyncio + async def test_fetch_portfolio( + self, + meta_servicer, + ): + meta_servicer.ping_responses.append(exchange_meta_pb.PingResponse()) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcMetaApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = meta_servicer + + result_ping = await api.fetch_ping() + expected_ping = {} + + assert result_ping == expected_ping + + @pytest.mark.asyncio + async def test_fetch_version( + self, + meta_servicer, + ): + version = "v1.12.28" + build = { + "GoVersion": "go1.20.5", + "GoArch": "amd64", + } + meta_servicer.version_responses.append( + exchange_meta_pb.VersionResponse( + version=version, + build=build, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcMetaApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = meta_servicer + + result_version = await api.fetch_version() + expected_version = {"build": build, "version": version} + + assert result_version == expected_version + + @pytest.mark.asyncio + async def test_fetch_info( + self, + meta_servicer, + ): + timestamp = 1698440196320 + server_time = 1698440197744 + version = "v1.12.28" + build = { + "GoVersion": "go1.20.5", + "GoArch": "amd64", + } + region = "test region" + meta_servicer.info_responses.append( + exchange_meta_pb.InfoResponse( + timestamp=timestamp, server_time=server_time, version=version, build=build, region=region + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcMetaApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = meta_servicer + + result_info = await api.fetch_info() + expected_info = { + "timestamp": str(timestamp), + "serverTime": str(server_time), + "version": version, + "build": build, + "region": region, + } + + assert result_info == expected_info + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py index d1814f37..f5579bee 100644 --- a/tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py @@ -37,7 +37,7 @@ async def test_fetch_portfolio( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) - api = IndexerGrpcAccountStream(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = IndexerGrpcAccountStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = account_servicer balance_updates = asyncio.Queue() diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_meta_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_meta_stream.py new file mode 100644 index 00000000..2a003c62 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_meta_stream.py @@ -0,0 +1,63 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_meta_rpc_pb2 as exchange_meta_pb +from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer + + +@pytest.fixture +def meta_servicer(): + return ConfigurableMetaQueryServicer() + + +class TestIndexerGrpcMetaStream: + @pytest.mark.asyncio + async def test_fetch_portfolio( + self, + meta_servicer, + ): + event = "test event" + new_endpoint = "new test endpoint" + timestamp = 1672218001897 + + meta_servicer.stream_keepalive_responses.append( + exchange_meta_pb.StreamKeepaliveResponse( + event=event, + new_endpoint=new_endpoint, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcMetaStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = meta_servicer + + keepalive_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: keepalive_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_keepalive( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = {"event": event, "newEndpoint": new_endpoint, "timestamp": str(timestamp)} + + first_update = await asyncio.wait_for(keepalive_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/core/tx/grpc/test_tx_grpc_api.py b/tests/core/tx/grpc/test_tx_grpc_api.py index fd04c68c..84b62817 100644 --- a/tests/core/tx/grpc/test_tx_grpc_api.py +++ b/tests/core/tx/grpc/test_tx_grpc_api.py @@ -40,7 +40,7 @@ async def test_simulate( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = TxGrpcApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = TxGrpcApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = tx_servicer result_simulate = await api.simulate(tx_bytes="Transaction content".encode()) @@ -103,7 +103,7 @@ async def test_get_tx( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = TxGrpcApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = TxGrpcApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = tx_servicer result_tx = await api.fetch_tx(hash=transaction_response.txhash) @@ -177,7 +177,7 @@ async def test_broadcast( network = Network.devnet() channel = grpc.aio.insecure_channel(network.grpc_endpoint) - api = TxGrpcApi(channel=channel, metadata_provider=self._dummy_metadata_provider()) + api = TxGrpcApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) api._stub = tx_servicer result_broadcast = await api.broadcast( diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index e347b231..70566069 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -7,12 +7,16 @@ from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query_pb from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service -from pyinjective.proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_pb +from pyinjective.proto.exchange import ( + injective_accounts_rpc_pb2 as exchange_accounts_pb, + injective_meta_rpc_pb2 as exchange_meta_pb, +) from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb from tests.client.chain.grpc.configurable_auth_query_servicer import ConfigurableAuthQueryServicer from tests.client.chain.grpc.configurable_autz_query_servicer import ConfigurableAuthZQueryServicer from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer +from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer from tests.core.tx.grpc.configurable_tx_query_servicer import ConfigurableTxQueryServicer @@ -36,6 +40,11 @@ def bank_servicer(): return ConfigurableBankQueryServicer() +@pytest.fixture +def meta_servicer(): + return ConfigurableMetaQueryServicer() + + @pytest.fixture def tx_servicer(): return ConfigurableTxQueryServicer() @@ -389,3 +398,75 @@ async def test_send_tx_block_mode_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. BLOCK broadcast mode should not be used" + + @pytest.mark.asyncio + async def test_ping_deprecation_warning( + self, + meta_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubMeta = meta_servicer + meta_servicer.ping_responses.append(exchange_meta_pb.PingResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.ping() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_ping instead" + + @pytest.mark.asyncio + async def test_version_deprecation_warning( + self, + meta_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubMeta = meta_servicer + meta_servicer.version_responses.append(exchange_meta_pb.VersionResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.version() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_version instead" + + @pytest.mark.asyncio + async def test_info_deprecation_warning( + self, + meta_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubMeta = meta_servicer + meta_servicer.info_responses.append(exchange_meta_pb.InfoResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.info() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_info instead" + + @pytest.mark.asyncio + async def test_stream_keepalive_deprecation_warning( + self, + meta_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + meta_servicer.stream_keepalive_responses.append(exchange_meta_pb.StreamKeepaliveResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_keepalive() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_keepalive instead" From 58ee139e2188e53f1a79d9b215c55dddd2dd418d Mon Sep 17 00:00:00 2001 From: abel Date: Tue, 31 Oct 2023 00:35:15 -0300 Subject: [PATCH 13/27] (feat) Implemented the low level API component for Indexer Oracle module, with the unit tests. Created new methods in AsyncClient to use them, and marked the ones directly using the stubs as deprecated --- .../oracle_rpc/1_StreamPrices.py | 34 +++++- .../exchange_client/oracle_rpc/2_Price.py | 2 +- .../oracle_rpc/3_OracleList.py | 2 +- pyinjective/async_client.py | 61 ++++++++++ .../indexer/grpc/indexer_grpc_oracle_api.py | 41 +++++++ .../grpc_stream/indexer_grpc_oracle_stream.py | 93 +++++++++++++++ .../utils/grpc_api_request_assistant.py | 2 +- .../configurable_oracle_query_servicer.py | 31 +++++ .../grpc/test_indexer_grpc_oracle_api.py | 86 ++++++++++++++ .../test_indexer_grpc_oracle_stream.py | 109 ++++++++++++++++++ .../test_async_client_deprecation_warnings.py | 88 ++++++++++++++ 11 files changed, 541 insertions(+), 8 deletions(-) create mode 100644 pyinjective/client/indexer/grpc/indexer_grpc_oracle_api.py create mode 100644 pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py create mode 100644 tests/client/indexer/configurable_oracle_query_servicer.py create mode 100644 tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py create mode 100644 tests/client/indexer/stream_grpc/test_indexer_grpc_oracle_stream.py diff --git a/examples/exchange_client/oracle_rpc/1_StreamPrices.py b/examples/exchange_client/oracle_rpc/1_StreamPrices.py index ca7f9de5..0a453690 100644 --- a/examples/exchange_client/oracle_rpc/1_StreamPrices.py +++ b/examples/exchange_client/oracle_rpc/1_StreamPrices.py @@ -1,21 +1,45 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def price_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to oracle prices updates ({exception})") + + +def stream_closed_processor(): + print("The oracle prices updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - base_symbol = "BTC" + base_symbol = "INJ" quote_symbol = "USDT" oracle_type = "bandibc" - oracle_prices = await client.stream_oracle_prices( - base_symbol=base_symbol, quote_symbol=quote_symbol, oracle_type=oracle_type + + task = asyncio.get_event_loop().create_task( + client.listen_oracle_prices_updates( + callback=price_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + ) ) - async for oracle in oracle_prices: - print(oracle) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/oracle_rpc/2_Price.py b/examples/exchange_client/oracle_rpc/2_Price.py index f3a5921e..b5a44a48 100644 --- a/examples/exchange_client/oracle_rpc/2_Price.py +++ b/examples/exchange_client/oracle_rpc/2_Price.py @@ -12,7 +12,7 @@ async def main() -> None: quote_symbol = "USDT" oracle_type = "bandibc" oracle_scale_factor = 6 - oracle_prices = await client.get_oracle_prices( + oracle_prices = await client.fetch_oracle_price( base_symbol=base_symbol, quote_symbol=quote_symbol, oracle_type=oracle_type, diff --git a/examples/exchange_client/oracle_rpc/3_OracleList.py b/examples/exchange_client/oracle_rpc/3_OracleList.py index 558ac66d..db2b7f03 100644 --- a/examples/exchange_client/oracle_rpc/3_OracleList.py +++ b/examples/exchange_client/oracle_rpc/3_OracleList.py @@ -8,7 +8,7 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - oracle_list = await client.get_oracle_list() + oracle_list = await client.fetch_oracle_list() print(oracle_list) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index b9de687d..a2f2b391 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -15,8 +15,10 @@ from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi +from pyinjective.client.indexer.grpc.indexer_grpc_oracle_api import IndexerGrpcOracleApi from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_oracle_stream import IndexerGrpcOracleStream from pyinjective.client.model.pagination import PaginationOption from pyinjective.composer import Composer from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket @@ -175,6 +177,12 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_oracle_api = IndexerGrpcOracleApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) self.exchange_account_stream_api = IndexerGrpcAccountStream( channel=self.exchange_channel, metadata_provider=lambda: self.network.exchange_metadata( @@ -187,6 +195,12 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_oracle_stream_api = IndexerGrpcOracleStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) async def all_tokens(self) -> Dict[str, Token]: if self._tokens is None: @@ -740,11 +754,33 @@ async def fetch_rewards(self, account_address: Optional[str] = None, epoch: Opti # OracleRPC async def stream_oracle_prices(self, base_symbol: str, quote_symbol: str, oracle_type: str): + """ + This method is deprecated and will be removed soon. Please use `listen_subaccount_balance_updates` instead + """ + warn("This method is deprecated. Use listen_oracle_prices_updates instead", DeprecationWarning, stacklevel=2) req = oracle_rpc_pb.StreamPricesRequest( base_symbol=base_symbol, quote_symbol=quote_symbol, oracle_type=oracle_type ) return self.stubOracle.StreamPrices(req) + async def listen_oracle_prices_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + base_symbol: Optional[str] = None, + quote_symbol: Optional[str] = None, + oracle_type: Optional[str] = None, + ): + await self.exchange_oracle_stream_api.stream_oracle_prices( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + ) + async def get_oracle_prices( self, base_symbol: str, @@ -752,6 +788,10 @@ async def get_oracle_prices( oracle_type: str, oracle_scale_factor: int, ): + """ + This method is deprecated and will be removed soon. Please use `fetch_oracle_price` instead + """ + warn("This method is deprecated. Use fetch_oracle_price instead", DeprecationWarning, stacklevel=2) req = oracle_rpc_pb.PriceRequest( base_symbol=base_symbol, quote_symbol=quote_symbol, @@ -760,10 +800,31 @@ async def get_oracle_prices( ) return await self.stubOracle.Price(req) + async def fetch_oracle_price( + self, + base_symbol: Optional[str] = None, + quote_symbol: Optional[str] = None, + oracle_type: Optional[str] = None, + oracle_scale_factor: Optional[int] = None, + ) -> Dict[str, Any]: + return await self.exchange_oracle_api.fetch_oracle_price( + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + oracle_scale_factor=oracle_scale_factor, + ) + async def get_oracle_list(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_oracle_list` instead + """ + warn("This method is deprecated. Use fetch_oracle_list instead", DeprecationWarning, stacklevel=2) req = oracle_rpc_pb.OracleListRequest() return await self.stubOracle.OracleList(req) + async def fetch_oracle_list(self) -> Dict[str, Any]: + return await self.exchange_oracle_api.fetch_oracle_list() + # InsuranceRPC async def get_insurance_funds(self): diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_oracle_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_oracle_api.py new file mode 100644 index 00000000..72416be7 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_oracle_api.py @@ -0,0 +1,41 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_oracle_rpc_pb2 as exchange_oracle_pb, + injective_oracle_rpc_pb2_grpc as exchange_oracle_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcOracleApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_oracle_grpc.InjectiveOracleRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_oracle_list(self) -> Dict[str, Any]: + request = exchange_oracle_pb.OracleListRequest() + response = await self._execute_call(call=self._stub.OracleList, request=request) + + return response + + async def fetch_oracle_price( + self, + base_symbol: Optional[str] = None, + quote_symbol: Optional[str] = None, + oracle_type: Optional[str] = None, + oracle_scale_factor: Optional[int] = None, + ) -> Dict[str, Any]: + request = exchange_oracle_pb.PriceRequest( + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + oracle_scale_factor=oracle_scale_factor, + ) + response = await self._execute_call(call=self._stub.Price, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py new file mode 100644 index 00000000..812632e4 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py @@ -0,0 +1,93 @@ +import asyncio +from typing import Callable, List, Optional + +from google.protobuf import json_format +from grpc import RpcError +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_oracle_rpc_pb2 as exchange_oracle_pb, + injective_oracle_rpc_pb2_grpc as exchange_oracle_grpc, +) + + +class IndexerGrpcOracleStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_oracle_grpc.InjectiveOracleRPCStub(channel) + self._metadata_provider = metadata_provider + + async def stream_oracle_prices( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + base_symbol: Optional[str] = None, + quote_symbol: Optional[str] = None, + oracle_type: Optional[str] = None, + ): + request = exchange_oracle_pb.StreamPricesRequest( + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + ) + metadata = await self._metadata_provider() + stream = self._stub.StreamPrices(request=request, metadata=metadata) + + try: + async for price_update in stream: + update = json_format.MessageToDict( + message=price_update, + including_default_value_fields=True, + ) + if asyncio.iscoroutinefunction(callback): + await callback(update) + else: + callback(update) + except RpcError as ex: + if on_status_callback is not None: + if asyncio.iscoroutinefunction(on_status_callback): + await on_status_callback(ex) + else: + on_status_callback(ex) + + if on_end_callback is not None: + if asyncio.iscoroutinefunction(on_end_callback): + await on_end_callback() + else: + on_end_callback() + + async def stream_oracle_prices_by_markets( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_oracle_pb.StreamPricesByMarketsRequest( + market_ids=market_ids, + ) + metadata = await self._metadata_provider() + stream = self._stub.StreamPricesByMarkets(request=request, metadata=metadata) + + try: + async for price_update in stream: + update = json_format.MessageToDict( + message=price_update, + including_default_value_fields=True, + ) + if asyncio.iscoroutinefunction(callback): + await callback(update) + else: + callback(update) + except RpcError as ex: + if on_status_callback is not None: + if asyncio.iscoroutinefunction(on_status_callback): + await on_status_callback(ex) + else: + on_status_callback(ex) + + if on_end_callback is not None: + if asyncio.iscoroutinefunction(on_end_callback): + await on_end_callback() + else: + on_end_callback() diff --git a/pyinjective/utils/grpc_api_request_assistant.py b/pyinjective/utils/grpc_api_request_assistant.py index df88e14c..5ed3c5fa 100644 --- a/pyinjective/utils/grpc_api_request_assistant.py +++ b/pyinjective/utils/grpc_api_request_assistant.py @@ -10,7 +10,7 @@ def __init__(self, metadata_provider: Callable): async def execute_call(self, call: Callable, request) -> Dict[str, Any]: metadata = await self._metadata_provider() - response = await call(request=request, metadata=metadata) + response = await call(request, metadata=metadata) result = json_format.MessageToDict( message=response, diff --git a/tests/client/indexer/configurable_oracle_query_servicer.py b/tests/client/indexer/configurable_oracle_query_servicer.py new file mode 100644 index 00000000..c7c06820 --- /dev/null +++ b/tests/client/indexer/configurable_oracle_query_servicer.py @@ -0,0 +1,31 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_oracle_rpc_pb2 as exchange_oracle_pb, + injective_oracle_rpc_pb2_grpc as exchange_oracle_grpc, +) + + +class ConfigurableOracleQueryServicer(exchange_oracle_grpc.InjectiveOracleRPCServicer): + def __init__(self): + super().__init__() + self.oracle_list_responses = deque() + self.price_responses = deque() + self.stream_prices_responses = deque() + self.stream_prices_by_markets_responses = deque() + + async def OracleList(self, request: exchange_oracle_pb.OracleListRequest, context=None, metadata=None): + return self.oracle_list_responses.pop() + + async def Price(self, request: exchange_oracle_pb.PriceRequest, context=None, metadata=None): + return self.price_responses.pop() + + async def StreamPrices(self, request: exchange_oracle_pb.StreamPricesRequest, context=None, metadata=None): + for event in self.stream_prices_responses: + yield event + + async def StreamPricesByMarkets( + self, request: exchange_oracle_pb.StreamPricesByMarketsRequest, context=None, metadata=None + ): + for event in self.stream_prices_by_markets_responses: + yield event diff --git a/tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py b/tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py new file mode 100644 index 00000000..fc8b546d --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py @@ -0,0 +1,86 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_oracle_api import IndexerGrpcOracleApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_oracle_rpc_pb2 as exchange_oracle_pb +from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer + + +@pytest.fixture +def oracle_servicer(): + return ConfigurableOracleQueryServicer() + + +class TestIndexerGrpcMetaApi: + @pytest.mark.asyncio + async def test_fetch_oracle_list( + self, + oracle_servicer, + ): + oracle = exchange_oracle_pb.Oracle( + symbol="Gold/USDT", + base_symbol="Gold", + quote_symbol="USDT", + oracle_type="pricefeed", + price="1", + ) + + oracle_servicer.oracle_list_responses.append( + exchange_oracle_pb.OracleListResponse( + oracles=[oracle], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcOracleApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = oracle_servicer + + result_oracle_list = await api.fetch_oracle_list() + expected_oracle_list = { + "oracles": [ + { + "symbol": oracle.symbol, + "baseSymbol": oracle.base_symbol, + "quoteSymbol": oracle.quote_symbol, + "oracleType": oracle.oracle_type, + "price": oracle.price, + } + ] + } + + assert result_oracle_list == expected_oracle_list + + @pytest.mark.asyncio + async def test_fetch_oracle_price( + self, + oracle_servicer, + ): + price = "0.00000002" + + oracle_servicer.price_responses.append( + exchange_oracle_pb.PriceResponse( + price=price, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcOracleApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = oracle_servicer + + result_oracle_list = await api.fetch_oracle_price( + base_symbol="Gold", + quote_symbol="USDT", + oracle_type="pricefeed", + oracle_scale_factor=6, + ) + expected_oracle_list = {"price": price} + + assert result_oracle_list == expected_oracle_list + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_oracle_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_oracle_stream.py new file mode 100644 index 00000000..3389948a --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_oracle_stream.py @@ -0,0 +1,109 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_oracle_stream import IndexerGrpcOracleStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_oracle_rpc_pb2 as exchange_oracle_pb +from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer + + +@pytest.fixture +def oracle_servicer(): + return ConfigurableOracleQueryServicer() + + +class TestIndexerGrpcOracleStream: + @pytest.mark.asyncio + async def test_stream_oracle_prices( + self, + oracle_servicer, + ): + price = "0.00000000000002" + timestamp = 1672218001897 + + oracle_servicer.stream_prices_responses.append( + exchange_oracle_pb.StreamPricesResponse( + price=price, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcOracleStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = oracle_servicer + + price_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: price_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_oracle_prices( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + base_symbol="Gold", + quote_symbol="USDT", + oracle_type="pricefeed", + ) + ) + expected_update = {"price": price, "timestamp": str(timestamp)} + + first_update = await asyncio.wait_for(price_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_oracle_prices_by_markets( + self, + oracle_servicer, + ): + price = "0.00000000000002" + timestamp = 1672218001897 + market_id = "0xa43d2be9861efb0d188b136cef0ae2150f80e08ec318392df654520dd359fcd7" + + oracle_servicer.stream_prices_by_markets_responses.append( + exchange_oracle_pb.StreamPricesByMarketsResponse( + price=price, + timestamp=timestamp, + market_id=market_id, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcOracleStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = oracle_servicer + + price_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: price_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_oracle_prices_by_markets( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[market_id], + ) + ) + expected_update = {"price": price, "timestamp": str(timestamp), "marketId": market_id} + + first_update = await asyncio.wait_for(price_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 70566069..722a2f82 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -10,6 +10,7 @@ from pyinjective.proto.exchange import ( injective_accounts_rpc_pb2 as exchange_accounts_pb, injective_meta_rpc_pb2 as exchange_meta_pb, + injective_oracle_rpc_pb2 as exchange_oracle_pb, ) from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb from tests.client.chain.grpc.configurable_auth_query_servicer import ConfigurableAuthQueryServicer @@ -17,6 +18,7 @@ from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer +from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer from tests.core.tx.grpc.configurable_tx_query_servicer import ConfigurableTxQueryServicer @@ -45,6 +47,11 @@ def meta_servicer(): return ConfigurableMetaQueryServicer() +@pytest.fixture +def oracle_servicer(): + return ConfigurableOracleQueryServicer() + + @pytest.fixture def tx_servicer(): return ConfigurableTxQueryServicer() @@ -470,3 +477,84 @@ async def test_stream_keepalive_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use listen_keepalive instead" + + @pytest.mark.asyncio + async def test_oracle_list_deprecation_warning( + self, + oracle_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubOracle = oracle_servicer + oracle_servicer.oracle_list_responses.append(exchange_oracle_pb.OracleListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_oracle_list() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_oracle_list instead" + + @pytest.mark.asyncio + async def test_get_oracle_list_deprecation_warning( + self, + oracle_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubOracle = oracle_servicer + oracle_servicer.oracle_list_responses.append(exchange_oracle_pb.OracleListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_oracle_list() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_oracle_list instead" + + @pytest.mark.asyncio + async def test_get_oracle_prices_deprecation_warning( + self, + oracle_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubOracle = oracle_servicer + oracle_servicer.price_responses.append(exchange_oracle_pb.PriceResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_oracle_prices( + base_symbol="Gold", + quote_symbol="USDT", + oracle_type="pricefeed", + oracle_scale_factor=6, + ) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_oracle_price instead" + + @pytest.mark.asyncio + async def test_stream_keepalive_deprecation_warning( + self, + oracle_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubOracle = oracle_servicer + oracle_servicer.stream_prices_responses.append(exchange_oracle_pb.StreamPricesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_oracle_prices( + base_symbol="Gold", + quote_symbol="USDT", + oracle_type="pricefeed", + ) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_oracle_prices_updates instead" From 78012161e4fa174806dca4fc54c986e2670d9957 Mon Sep 17 00:00:00 2001 From: abel Date: Tue, 31 Oct 2023 13:01:32 -0300 Subject: [PATCH 14/27] (feat) Implemented low level API components for Indexer Insurance module, with unit tests. Created new functions to access them in AsyncClient and marked the old functions using the stub directly as deprecated --- .../insurance_rpc/1_InsuranceFunds.py | 2 +- .../insurance_rpc/2_Redemptions.py | 4 +- pyinjective/async_client.py | 30 +++++ .../grpc/indexer_grpc_insurance_api.py | 39 ++++++ .../configurable_insurance_query_servicer.py | 19 +++ .../grpc/test_indexer_grpc_insurance_api.py | 123 ++++++++++++++++++ .../grpc/test_indexer_grpc_oracle_api.py | 2 +- .../test_async_client_deprecation_warnings.py | 43 ++++++ 8 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 pyinjective/client/indexer/grpc/indexer_grpc_insurance_api.py create mode 100644 tests/client/indexer/configurable_insurance_query_servicer.py create mode 100644 tests/client/indexer/grpc/test_indexer_grpc_insurance_api.py diff --git a/examples/exchange_client/insurance_rpc/1_InsuranceFunds.py b/examples/exchange_client/insurance_rpc/1_InsuranceFunds.py index 167cae93..4f962b08 100644 --- a/examples/exchange_client/insurance_rpc/1_InsuranceFunds.py +++ b/examples/exchange_client/insurance_rpc/1_InsuranceFunds.py @@ -8,7 +8,7 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - insurance_funds = await client.get_insurance_funds() + insurance_funds = await client.fetch_insurance_funds() print(insurance_funds) diff --git a/examples/exchange_client/insurance_rpc/2_Redemptions.py b/examples/exchange_client/insurance_rpc/2_Redemptions.py index 794e4b02..2118f210 100644 --- a/examples/exchange_client/insurance_rpc/2_Redemptions.py +++ b/examples/exchange_client/insurance_rpc/2_Redemptions.py @@ -11,9 +11,7 @@ async def main() -> None: redeemer = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" redemption_denom = "share4" status = "disbursed" - insurance_redemptions = await client.get_redemptions( - redeemer=redeemer, redemption_denom=redemption_denom, status=status - ) + insurance_redemptions = await client.fetch_redemptions(address=redeemer, denom=redemption_denom, status=status) print(insurance_redemptions) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index a2f2b391..50bce186 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -14,6 +14,7 @@ from pyinjective.client.chain.grpc.chain_grpc_authz_api import ChainGrpcAuthZApi from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi +from pyinjective.client.indexer.grpc.indexer_grpc_insurance_api import IndexerGrpcInsuranceApi from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi from pyinjective.client.indexer.grpc.indexer_grpc_oracle_api import IndexerGrpcOracleApi from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream @@ -171,6 +172,12 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_insurance_api = IndexerGrpcInsuranceApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) self.exchange_meta_api = IndexerGrpcMetaApi( channel=self.exchange_channel, metadata_provider=lambda: self.network.exchange_metadata( @@ -828,10 +835,21 @@ async def fetch_oracle_list(self) -> Dict[str, Any]: # InsuranceRPC async def get_insurance_funds(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_insurance_funds` instead + """ + warn("This method is deprecated. Use fetch_insurance_funds instead", DeprecationWarning, stacklevel=2) req = insurance_rpc_pb.FundsRequest() return await self.stubInsurance.Funds(req) + async def fetch_insurance_funds(self) -> Dict[str, Any]: + return await self.exchange_insurance_api.fetch_insurance_funds() + async def get_redemptions(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_redemptions` instead + """ + warn("This method is deprecated. Use fetch_redemptions instead", DeprecationWarning, stacklevel=2) req = insurance_rpc_pb.RedemptionsRequest( redeemer=kwargs.get("redeemer"), redemption_denom=kwargs.get("redemption_denom"), @@ -839,6 +857,18 @@ async def get_redemptions(self, **kwargs): ) return await self.stubInsurance.Redemptions(req) + async def fetch_redemptions( + self, + address: Optional[str] = None, + denom: Optional[str] = None, + status: Optional[str] = None, + ) -> Dict[str, Any]: + return await self.exchange_insurance_api.fetch_redemptions( + address=address, + denom=denom, + status=status, + ) + # SpotRPC async def get_spot_market(self, market_id: str): diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_insurance_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_insurance_api.py new file mode 100644 index 00000000..95476677 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_insurance_api.py @@ -0,0 +1,39 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_insurance_rpc_pb2 as exchange_insurance_pb, + injective_insurance_rpc_pb2_grpc as exchange_insurance_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcInsuranceApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_insurance_grpc.InjectiveInsuranceRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_insurance_funds(self) -> Dict[str, Any]: + request = exchange_insurance_pb.FundsRequest() + response = await self._execute_call(call=self._stub.Funds, request=request) + + return response + + async def fetch_redemptions( + self, + address: Optional[str] = None, + denom: Optional[str] = None, + status: Optional[str] = None, + ) -> Dict[str, Any]: + request = exchange_insurance_pb.RedemptionsRequest( + redeemer=address, + redemption_denom=denom, + status=status, + ) + response = await self._execute_call(call=self._stub.Redemptions, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/tests/client/indexer/configurable_insurance_query_servicer.py b/tests/client/indexer/configurable_insurance_query_servicer.py new file mode 100644 index 00000000..aa4e0491 --- /dev/null +++ b/tests/client/indexer/configurable_insurance_query_servicer.py @@ -0,0 +1,19 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_insurance_rpc_pb2 as exchange_insurance_pb, + injective_insurance_rpc_pb2_grpc as exchange_insurance_grpc, +) + + +class ConfigurableInsuranceQueryServicer(exchange_insurance_grpc.InjectiveInsuranceRPCServicer): + def __init__(self): + super().__init__() + self.funds_responses = deque() + self.redemptions_responses = deque() + + async def Funds(self, request: exchange_insurance_pb.FundsRequest, context=None, metadata=None): + return self.funds_responses.pop() + + async def Redemptions(self, request: exchange_insurance_pb.RedemptionsRequest, context=None, metadata=None): + return self.redemptions_responses.pop() diff --git a/tests/client/indexer/grpc/test_indexer_grpc_insurance_api.py b/tests/client/indexer/grpc/test_indexer_grpc_insurance_api.py new file mode 100644 index 00000000..2499b7db --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_insurance_api.py @@ -0,0 +1,123 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_insurance_api import IndexerGrpcInsuranceApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_insurance_rpc_pb2 as exchange_insurance_pb +from tests.client.indexer.configurable_insurance_query_servicer import ConfigurableInsuranceQueryServicer + + +@pytest.fixture +def insurance_servicer(): + return ConfigurableInsuranceQueryServicer() + + +class TestIndexerGrpcInsuranceApi: + @pytest.mark.asyncio + async def test_fetch_insurance_funds( + self, + insurance_servicer, + ): + insurance_fund = exchange_insurance_pb.InsuranceFund( + market_ticker="inj/usdt", + market_id="0x7f15b4f4484e6820fc446e42cd447ca6d9bfd7c0592304294270c2bef5f589cd", + deposit_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + pool_token_denom="share132", + redemption_notice_period_duration=1209600, + balance="920389040000", + total_share="1000000000000000000", + oracle_base="0x31775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae", + oracle_quote="USD", + oracle_type="coinbase", + expiry=1696539600, + ) + + insurance_servicer.funds_responses.append( + exchange_insurance_pb.FundsResponse( + funds=[insurance_fund], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcInsuranceApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = insurance_servicer + + result_insurance_list = await api.fetch_insurance_funds() + expected_insurance_list = { + "funds": [ + { + "marketTicker": insurance_fund.market_ticker, + "marketId": insurance_fund.market_id, + "depositDenom": insurance_fund.deposit_denom, + "poolTokenDenom": insurance_fund.pool_token_denom, + "redemptionNoticePeriodDuration": str(insurance_fund.redemption_notice_period_duration), + "balance": insurance_fund.balance, + "totalShare": insurance_fund.total_share, + "oracleBase": insurance_fund.oracle_base, + "oracleQuote": insurance_fund.oracle_quote, + "oracleType": insurance_fund.oracle_type, + "expiry": str(insurance_fund.expiry), + } + ] + } + + assert result_insurance_list == expected_insurance_list + + @pytest.mark.asyncio + async def test_fetch_redemptions( + self, + insurance_servicer, + ): + redemption_schedule = exchange_insurance_pb.RedemptionSchedule( + redemption_id=1, + status="disbursed", + redeemer="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + claimable_redemption_time=1674798129093000, + redemption_amount="500000000000000000", + redemption_denom="share4", + requested_at=1673588529093000, + disbursed_amount="5000000", + disbursed_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + disbursed_at=1674798130965000, + ) + + insurance_servicer.redemptions_responses.append( + exchange_insurance_pb.RedemptionsResponse( + redemption_schedules=[redemption_schedule], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcInsuranceApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = insurance_servicer + + result_insurance_list = await api.fetch_redemptions( + address=redemption_schedule.redeemer, + denom=redemption_schedule.redemption_denom, + status=redemption_schedule.status, + ) + expected_insurance_list = { + "redemptionSchedules": [ + { + "redemptionId": str(redemption_schedule.redemption_id), + "status": redemption_schedule.status, + "redeemer": redemption_schedule.redeemer, + "claimableRedemptionTime": str(redemption_schedule.claimable_redemption_time), + "redemptionAmount": str(redemption_schedule.redemption_amount), + "redemptionDenom": redemption_schedule.redemption_denom, + "requestedAt": str(redemption_schedule.requested_at), + "disbursedAmount": redemption_schedule.disbursed_amount, + "disbursedDenom": redemption_schedule.disbursed_denom, + "disbursedAt": str(redemption_schedule.disbursed_at), + } + ] + } + + assert result_insurance_list == expected_insurance_list + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py b/tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py index fc8b546d..25c39733 100644 --- a/tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py +++ b/tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py @@ -12,7 +12,7 @@ def oracle_servicer(): return ConfigurableOracleQueryServicer() -class TestIndexerGrpcMetaApi: +class TestIndexerGrpcOracleApi: @pytest.mark.asyncio async def test_fetch_oracle_list( self, diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 722a2f82..a5a8c6c0 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -9,6 +9,7 @@ from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service from pyinjective.proto.exchange import ( injective_accounts_rpc_pb2 as exchange_accounts_pb, + injective_insurance_rpc_pb2 as exchange_insurance_pb, injective_meta_rpc_pb2 as exchange_meta_pb, injective_oracle_rpc_pb2 as exchange_oracle_pb, ) @@ -17,6 +18,7 @@ from tests.client.chain.grpc.configurable_autz_query_servicer import ConfigurableAuthZQueryServicer from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer +from tests.client.indexer.configurable_insurance_query_servicer import ConfigurableInsuranceQueryServicer from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer from tests.core.tx.grpc.configurable_tx_query_servicer import ConfigurableTxQueryServicer @@ -42,6 +44,11 @@ def bank_servicer(): return ConfigurableBankQueryServicer() +@pytest.fixture +def insurance_servicer(): + return ConfigurableInsuranceQueryServicer() + + @pytest.fixture def meta_servicer(): return ConfigurableMetaQueryServicer() @@ -558,3 +565,39 @@ async def test_stream_keepalive_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use listen_oracle_prices_updates instead" + + @pytest.mark.asyncio + async def test_get_insurance_funds_deprecation_warning( + self, + insurance_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubInsurance = insurance_servicer + insurance_servicer.funds_responses.append(exchange_insurance_pb.FundsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_insurance_funds() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_insurance_funds instead" + + @pytest.mark.asyncio + async def test_get_redemptions_deprecation_warning( + self, + insurance_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubInsurance = insurance_servicer + insurance_servicer.redemptions_responses.append(exchange_insurance_pb.RedemptionsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_redemptions() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_redemptions instead" From bd464122456521068cbbc0f89b4fbee38ed4705f Mon Sep 17 00:00:00 2001 From: abel Date: Tue, 31 Oct 2023 18:10:35 -0300 Subject: [PATCH 15/27] (feat) Implemented the low level API components for the Indexer Auction module, with tests. Added new functions in AsyncClient to use the component and marked the old functions using the stub directly as deprecated --- .../exchange_client/auctions_rpc/1_Auction.py | 2 +- .../auctions_rpc/2_Auctions.py | 2 +- .../auctions_rpc/3_StreamBids.py | 29 ++++- pyinjective/async_client.py | 45 +++++++ .../indexer/grpc/indexer_grpc_auction_api.py | 30 +++++ .../indexer_grpc_account_stream.py | 37 ++---- .../indexer_grpc_auction_stream.py | 31 +++++ .../grpc_stream/indexer_grpc_meta_stream.py | 37 ++---- .../grpc_stream/indexer_grpc_oracle_stream.py | 68 +++------- .../utils/grpc_api_stream_assistant.py | 45 +++++++ .../configurable_auction_query_servicer.py | 24 ++++ .../grpc/test_indexer_grpc_auction_api.py | 121 ++++++++++++++++++ .../test_indexer_grpc_auction_stream.py | 65 ++++++++++ .../test_async_client_deprecation_warnings.py | 61 +++++++++ 14 files changed, 484 insertions(+), 113 deletions(-) create mode 100644 pyinjective/client/indexer/grpc/indexer_grpc_auction_api.py create mode 100644 pyinjective/client/indexer/grpc_stream/indexer_grpc_auction_stream.py create mode 100644 pyinjective/utils/grpc_api_stream_assistant.py create mode 100644 tests/client/indexer/configurable_auction_query_servicer.py create mode 100644 tests/client/indexer/grpc/test_indexer_grpc_auction_api.py create mode 100644 tests/client/indexer/stream_grpc/test_indexer_grpc_auction_stream.py diff --git a/examples/exchange_client/auctions_rpc/1_Auction.py b/examples/exchange_client/auctions_rpc/1_Auction.py index 94f73e16..71fbd36c 100644 --- a/examples/exchange_client/auctions_rpc/1_Auction.py +++ b/examples/exchange_client/auctions_rpc/1_Auction.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) bid_round = 31 - auction = await client.get_auction(bid_round=bid_round) + auction = await client.fetch_auction(round=bid_round) print(auction) diff --git a/examples/exchange_client/auctions_rpc/2_Auctions.py b/examples/exchange_client/auctions_rpc/2_Auctions.py index 30576a89..f2b7f7bf 100644 --- a/examples/exchange_client/auctions_rpc/2_Auctions.py +++ b/examples/exchange_client/auctions_rpc/2_Auctions.py @@ -8,7 +8,7 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - auctions = await client.get_auctions() + auctions = await client.fetch_auctions() print(auctions) diff --git a/examples/exchange_client/auctions_rpc/3_StreamBids.py b/examples/exchange_client/auctions_rpc/3_StreamBids.py index 8306becb..8bfceba1 100644 --- a/examples/exchange_client/auctions_rpc/3_StreamBids.py +++ b/examples/exchange_client/auctions_rpc/3_StreamBids.py @@ -1,16 +1,39 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def bid_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to bids updates ({exception})") + + +def stream_closed_processor(): + print("The bids updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - bids = await client.stream_bids() - async for bid in bids: - print(bid) + + task = asyncio.get_event_loop().create_task( + client.listen_bids_updates( + callback=bid_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 50bce186..c9c4d4fd 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -14,10 +14,12 @@ from pyinjective.client.chain.grpc.chain_grpc_authz_api import ChainGrpcAuthZApi from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi +from pyinjective.client.indexer.grpc.indexer_grpc_auction_api import IndexerGrpcAuctionApi from pyinjective.client.indexer.grpc.indexer_grpc_insurance_api import IndexerGrpcInsuranceApi from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi from pyinjective.client.indexer.grpc.indexer_grpc_oracle_api import IndexerGrpcOracleApi from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_auction_stream import IndexerGrpcAuctionStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_oracle_stream import IndexerGrpcOracleStream from pyinjective.client.model.pagination import PaginationOption @@ -172,6 +174,12 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_auction_api = IndexerGrpcAuctionApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) self.exchange_insurance_api = IndexerGrpcInsuranceApi( channel=self.exchange_channel, metadata_provider=lambda: self.network.exchange_metadata( @@ -190,12 +198,19 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_account_stream_api = IndexerGrpcAccountStream( channel=self.exchange_channel, metadata_provider=lambda: self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_auction_stream_api = IndexerGrpcAuctionStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) self.exchange_meta_stream_api = IndexerGrpcMetaStream( channel=self.exchange_channel, metadata_provider=lambda: self.network.exchange_metadata( @@ -438,17 +453,47 @@ async def fetch_bank_balance(self, address: str, denom: str) -> Dict[str, Any]: # Auction RPC async def get_auction(self, bid_round: int): + """ + This method is deprecated and will be removed soon. Please use `fetch_auction` instead + """ + warn("This method is deprecated. Use fetch_auction instead", DeprecationWarning, stacklevel=2) req = auction_rpc_pb.AuctionEndpointRequest(round=bid_round) return await self.stubAuction.AuctionEndpoint(req) + async def fetch_auction(self, round: int) -> Dict[str, Any]: + return await self.exchange_auction_api.fetch_auction(round=round) + async def get_auctions(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_auctions` instead + """ + warn("This method is deprecated. Use fetch_auctions instead", DeprecationWarning, stacklevel=2) req = auction_rpc_pb.AuctionsRequest() return await self.stubAuction.Auctions(req) + async def fetch_auctions(self) -> Dict[str, Any]: + return await self.exchange_auction_api.fetch_auctions() + async def stream_bids(self): + """ + This method is deprecated and will be removed soon. Please use `listen_bids_updates` instead + """ + warn("This method is deprecated. Use listen_bids_updates instead", DeprecationWarning, stacklevel=2) req = auction_rpc_pb.StreamBidsRequest() return self.stubAuction.StreamBids(req) + async def listen_bids_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_auction_stream_api.stream_bids( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + # Meta RPC async def ping(self): diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_auction_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_auction_api.py new file mode 100644 index 00000000..c9b9d853 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_auction_api.py @@ -0,0 +1,30 @@ +from typing import Any, Callable, Dict + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_auction_rpc_pb2 as exchange_auction_pb, + injective_auction_rpc_pb2_grpc as exchange_auction_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcAuctionApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_auction_grpc.InjectiveAuctionRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_auction(self, round: int) -> Dict[str, Any]: + request = exchange_auction_pb.AuctionEndpointRequest(round=round) + response = await self._execute_call(call=self._stub.AuctionEndpoint, request=request) + + return response + + async def fetch_auctions(self) -> Dict[str, Any]: + request = exchange_auction_pb.AuctionsRequest() + response = await self._execute_call(call=self._stub.Auctions, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py index b1bd9564..04ba1af3 100644 --- a/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py @@ -1,20 +1,18 @@ -import asyncio from typing import Callable, List, Optional -from google.protobuf import json_format -from grpc import RpcError from grpc.aio import Channel from pyinjective.proto.exchange import ( injective_accounts_rpc_pb2 as exchange_accounts_pb, injective_accounts_rpc_pb2_grpc as exchange_accounts_grpc, ) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant class IndexerGrpcAccountStream: def __init__(self, channel: Channel, metadata_provider: Callable): self._stub = self._stub = exchange_accounts_grpc.InjectiveAccountsRPCStub(channel) - self._metadata_provider = metadata_provider + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) async def stream_subaccount_balance( self, @@ -28,28 +26,11 @@ async def stream_subaccount_balance( subaccount_id=subaccount_id, denoms=denoms, ) - metadata = await self._metadata_provider() - stream = self._stub.StreamSubaccountBalance(request=request, metadata=metadata) - try: - async for balance_update in stream: - update = json_format.MessageToDict( - message=balance_update, - including_default_value_fields=True, - ) - if asyncio.iscoroutinefunction(callback): - await callback(update) - else: - callback(update) - except RpcError as ex: - if on_status_callback is not None: - if asyncio.iscoroutinefunction(on_status_callback): - await on_status_callback(ex) - else: - on_status_callback(ex) - - if on_end_callback is not None: - if asyncio.iscoroutinefunction(on_end_callback): - await on_end_callback() - else: - on_end_callback() + await self._assistant.listen_stream( + call=self._stub.StreamSubaccountBalance, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_auction_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_auction_stream.py new file mode 100644 index 00000000..43009ee9 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_auction_stream.py @@ -0,0 +1,31 @@ +from typing import Callable, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_auction_rpc_pb2 as exchange_auction_pb, + injective_auction_rpc_pb2_grpc as exchange_auction_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcAuctionStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_auction_grpc.InjectiveAuctionRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_bids( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_auction_pb.StreamBidsRequest() + + await self._assistant.listen_stream( + call=self._stub.StreamBids, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py index e43c5968..aeff1132 100644 --- a/pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py @@ -1,20 +1,18 @@ -import asyncio from typing import Callable, Optional -from google.protobuf import json_format -from grpc import RpcError from grpc.aio import Channel from pyinjective.proto.exchange import ( injective_meta_rpc_pb2 as exchange_meta_pb, injective_meta_rpc_pb2_grpc as exchange_meta_grpc, ) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant class IndexerGrpcMetaStream: def __init__(self, channel: Channel, metadata_provider: Callable): self._stub = self._stub = exchange_meta_grpc.InjectiveMetaRPCStub(channel) - self._metadata_provider = metadata_provider + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) async def stream_keepalive( self, @@ -23,28 +21,11 @@ async def stream_keepalive( on_status_callback: Optional[Callable] = None, ): request = exchange_meta_pb.StreamKeepaliveRequest() - metadata = await self._metadata_provider() - stream = self._stub.StreamKeepalive(request=request, metadata=metadata) - try: - async for keepalive_update in stream: - update = json_format.MessageToDict( - message=keepalive_update, - including_default_value_fields=True, - ) - if asyncio.iscoroutinefunction(callback): - await callback(update) - else: - callback(update) - except RpcError as ex: - if on_status_callback is not None: - if asyncio.iscoroutinefunction(on_status_callback): - await on_status_callback(ex) - else: - on_status_callback(ex) - - if on_end_callback is not None: - if asyncio.iscoroutinefunction(on_end_callback): - await on_end_callback() - else: - on_end_callback() + await self._assistant.listen_stream( + call=self._stub.StreamKeepalive, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py index 812632e4..0bad6e35 100644 --- a/pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py @@ -1,20 +1,18 @@ -import asyncio from typing import Callable, List, Optional -from google.protobuf import json_format -from grpc import RpcError from grpc.aio import Channel from pyinjective.proto.exchange import ( injective_oracle_rpc_pb2 as exchange_oracle_pb, injective_oracle_rpc_pb2_grpc as exchange_oracle_grpc, ) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant class IndexerGrpcOracleStream: def __init__(self, channel: Channel, metadata_provider: Callable): self._stub = self._stub = exchange_oracle_grpc.InjectiveOracleRPCStub(channel) - self._metadata_provider = metadata_provider + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) async def stream_oracle_prices( self, @@ -30,31 +28,14 @@ async def stream_oracle_prices( quote_symbol=quote_symbol, oracle_type=oracle_type, ) - metadata = await self._metadata_provider() - stream = self._stub.StreamPrices(request=request, metadata=metadata) - try: - async for price_update in stream: - update = json_format.MessageToDict( - message=price_update, - including_default_value_fields=True, - ) - if asyncio.iscoroutinefunction(callback): - await callback(update) - else: - callback(update) - except RpcError as ex: - if on_status_callback is not None: - if asyncio.iscoroutinefunction(on_status_callback): - await on_status_callback(ex) - else: - on_status_callback(ex) - - if on_end_callback is not None: - if asyncio.iscoroutinefunction(on_end_callback): - await on_end_callback() - else: - on_end_callback() + await self._assistant.listen_stream( + call=self._stub.StreamPrices, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) async def stream_oracle_prices_by_markets( self, @@ -66,28 +47,11 @@ async def stream_oracle_prices_by_markets( request = exchange_oracle_pb.StreamPricesByMarketsRequest( market_ids=market_ids, ) - metadata = await self._metadata_provider() - stream = self._stub.StreamPricesByMarkets(request=request, metadata=metadata) - try: - async for price_update in stream: - update = json_format.MessageToDict( - message=price_update, - including_default_value_fields=True, - ) - if asyncio.iscoroutinefunction(callback): - await callback(update) - else: - callback(update) - except RpcError as ex: - if on_status_callback is not None: - if asyncio.iscoroutinefunction(on_status_callback): - await on_status_callback(ex) - else: - on_status_callback(ex) - - if on_end_callback is not None: - if asyncio.iscoroutinefunction(on_end_callback): - await on_end_callback() - else: - on_end_callback() + await self._assistant.listen_stream( + call=self._stub.StreamPricesByMarkets, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/utils/grpc_api_stream_assistant.py b/pyinjective/utils/grpc_api_stream_assistant.py new file mode 100644 index 00000000..50092def --- /dev/null +++ b/pyinjective/utils/grpc_api_stream_assistant.py @@ -0,0 +1,45 @@ +import asyncio +from typing import Callable, Optional + +from google.protobuf import json_format +from grpc import RpcError + + +class GrpcApiStreamAssistant: + def __init__(self, metadata_provider: Callable): + super().__init__() + self._metadata_provider = metadata_provider + + async def listen_stream( + self, + call: Callable, + request, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + metadata = await self._metadata_provider() + stream = call(request, metadata=metadata) + + try: + async for event in stream: + parsed_event = json_format.MessageToDict( + message=event, + including_default_value_fields=True, + ) + if asyncio.iscoroutinefunction(callback): + await callback(parsed_event) + else: + callback(parsed_event) + except RpcError as ex: + if on_status_callback is not None: + if asyncio.iscoroutinefunction(on_status_callback): + await on_status_callback(ex) + else: + on_status_callback(ex) + + if on_end_callback is not None: + if asyncio.iscoroutinefunction(on_end_callback): + await on_end_callback() + else: + on_end_callback() diff --git a/tests/client/indexer/configurable_auction_query_servicer.py b/tests/client/indexer/configurable_auction_query_servicer.py new file mode 100644 index 00000000..606e2e0d --- /dev/null +++ b/tests/client/indexer/configurable_auction_query_servicer.py @@ -0,0 +1,24 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_auction_rpc_pb2 as exchange_auction_pb, + injective_auction_rpc_pb2_grpc as exchange_auction_grpc, +) + + +class ConfigurableAuctionQueryServicer(exchange_auction_grpc.InjectiveAuctionRPCServicer): + def __init__(self): + super().__init__() + self.auction_endpoint_responses = deque() + self.auctions_responses = deque() + self.stream_bids_responses = deque() + + async def AuctionEndpoint(self, request: exchange_auction_pb.AuctionEndpointRequest, context=None, metadata=None): + return self.auction_endpoint_responses.pop() + + async def Auctions(self, request: exchange_auction_pb.AuctionsRequest, context=None, metadata=None): + return self.auctions_responses.pop() + + async def StreamBids(self, request: exchange_auction_pb.StreamBidsRequest, context=None, metadata=None): + for event in self.stream_bids_responses: + yield event diff --git a/tests/client/indexer/grpc/test_indexer_grpc_auction_api.py b/tests/client/indexer/grpc/test_indexer_grpc_auction_api.py new file mode 100644 index 00000000..8be68ddb --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_auction_api.py @@ -0,0 +1,121 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_auction_api import IndexerGrpcAuctionApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_auction_rpc_pb2 as exchange_auction_pb +from tests.client.indexer.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer + + +@pytest.fixture +def auction_servicer(): + return ConfigurableAuctionQueryServicer() + + +class TestIndexerGrpcAuctionApi: + @pytest.mark.asyncio + async def test_fetch_auction( + self, + auction_servicer, + ): + coin = exchange_auction_pb.Coin( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + amount="2322098", + ) + auction = exchange_auction_pb.Auction( + winner="inj1uyk56r3xdcf60jwrmn7p9rgla9dc4gam56ajrq", + basket=[coin], + winning_bid_amount="2000000000000000000", + round=31, + end_timestamp=1676013187000, + updated_at=1677075140258, + ) + + bid = exchange_auction_pb.Bid( + bidder="inj1pdxq82m20fzkjn2th2mm5jp7t5ex6j6klf9cs5", + amount="1000000000000000000", + timestamp=1675426622603, + ) + + auction_servicer.auction_endpoint_responses.append( + exchange_auction_pb.AuctionEndpointResponse( + auction=auction, + bids=[bid], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auction_servicer + + result_auction = await api.fetch_auction(round=auction.round) + expected_auction = { + "auction": { + "winner": auction.winner, + "basket": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "winningBidAmount": auction.winning_bid_amount, + "round": str(auction.round), + "endTimestamp": str(auction.end_timestamp), + "updatedAt": str(auction.updated_at), + }, + "bids": [{"amount": bid.amount, "bidder": bid.bidder, "timestamp": str(bid.timestamp)}], + } + + assert result_auction == expected_auction + + @pytest.mark.asyncio + async def test_fetch_auctions( + self, + auction_servicer, + ): + coin = exchange_auction_pb.Coin( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + amount="2322098", + ) + auction = exchange_auction_pb.Auction( + winner="inj1uyk56r3xdcf60jwrmn7p9rgla9dc4gam56ajrq", + basket=[coin], + winning_bid_amount="2000000000000000000", + round=31, + end_timestamp=1676013187000, + updated_at=1677075140258, + ) + + auction_servicer.auctions_responses.append(exchange_auction_pb.AuctionsResponse(auctions=[auction])) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auction_servicer + + result_auctions = await api.fetch_auctions() + expected_auctions = { + "auctions": [ + { + "winner": auction.winner, + "basket": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "winningBidAmount": auction.winning_bid_amount, + "round": str(auction.round), + "endTimestamp": str(auction.end_timestamp), + "updatedAt": str(auction.updated_at), + } + ] + } + + assert result_auctions == expected_auctions + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_auction_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_auction_stream.py new file mode 100644 index 00000000..8c88d717 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_auction_stream.py @@ -0,0 +1,65 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_auction_stream import IndexerGrpcAuctionStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_auction_rpc_pb2 as exchange_auction_pb +from tests.client.indexer.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer + + +@pytest.fixture +def auction_servicer(): + return ConfigurableAuctionQueryServicer() + + +class TestIndexerGrpcAuctionStream: + @pytest.mark.asyncio + async def test_stream_oracle_prices_by_markets( + self, + auction_servicer, + ): + bidder = "inj1pdxq82m20fzkjn2th2mm5jp7t5ex6j6klf9cs5" + amount = "1000000000000000000" + round = 1 + timestamp = 1675426622603 + + auction_servicer.stream_bids_responses.append( + exchange_auction_pb.StreamBidsResponse(bidder=bidder, bid_amount=amount, round=round, timestamp=timestamp) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAuctionStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auction_servicer + + bid_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: bid_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_bids( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "bidAmount": amount, + "bidder": bidder, + "round": str(round), + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(bid_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index a5a8c6c0..4fda5760 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -9,6 +9,7 @@ from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service from pyinjective.proto.exchange import ( injective_accounts_rpc_pb2 as exchange_accounts_pb, + injective_auction_rpc_pb2 as exchange_auction_pb, injective_insurance_rpc_pb2 as exchange_insurance_pb, injective_meta_rpc_pb2 as exchange_meta_pb, injective_oracle_rpc_pb2 as exchange_oracle_pb, @@ -18,6 +19,7 @@ from tests.client.chain.grpc.configurable_autz_query_servicer import ConfigurableAuthZQueryServicer from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer +from tests.client.indexer.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer from tests.client.indexer.configurable_insurance_query_servicer import ConfigurableInsuranceQueryServicer from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer @@ -29,6 +31,11 @@ def account_servicer(): return ConfigurableAccountQueryServicer() +@pytest.fixture +def auction_servicer(): + return ConfigurableAuctionQueryServicer() + + @pytest.fixture def auth_servicer(): return ConfigurableAuthQueryServicer() @@ -601,3 +608,57 @@ async def test_get_redemptions_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_redemptions instead" + + @pytest.mark.asyncio + async def test_get_auction_deprecation_warning( + self, + auction_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubAuction = auction_servicer + auction_servicer.auction_endpoint_responses.append(exchange_auction_pb.AuctionEndpointResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_auction(bid_round=1) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_auction instead" + + @pytest.mark.asyncio + async def test_get_auctions_deprecation_warning( + self, + auction_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubAuction = auction_servicer + auction_servicer.auctions_responses.append(exchange_auction_pb.AuctionsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_auctions() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_auctions instead" + + @pytest.mark.asyncio + async def test_stream_bids_deprecation_warning( + self, + auction_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubAuction = auction_servicer + auction_servicer.stream_bids_responses.append(exchange_auction_pb.StreamBidsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_bids() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_bids_updates instead" From e080b0ff0a36a5672d99ff7b3fd78550ba90bd74 Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 3 Nov 2023 10:42:07 -0300 Subject: [PATCH 16/27] (feat) Started implementing the Indexer Spot Exchange low level API components --- .../spot_exchange_rpc/1_Market.py | 2 +- .../spot_exchange_rpc/2_Markets.py | 4 +- .../spot_exchange_rpc/4_Orderbook.py | 2 +- pyinjective/async_client.py | 134 ++++++---- .../indexer/grpc/indexer_grpc_spot_api.py | 45 ++++ pyinjective/utils/fetch_metadata.py | 50 ++-- .../configurable_spot_query_servicer.py | 23 ++ .../grpc/test_indexer_grpc_spot_api.py | 250 ++++++++++++++++++ .../test_async_client_deprecation_warnings.py | 61 +++++ 9 files changed, 495 insertions(+), 76 deletions(-) create mode 100644 pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py create mode 100644 tests/client/indexer/configurable_spot_query_servicer.py create mode 100644 tests/client/indexer/grpc/test_indexer_grpc_spot_api.py diff --git a/examples/exchange_client/spot_exchange_rpc/1_Market.py b/examples/exchange_client/spot_exchange_rpc/1_Market.py index d926e97c..e8705d68 100644 --- a/examples/exchange_client/spot_exchange_rpc/1_Market.py +++ b/examples/exchange_client/spot_exchange_rpc/1_Market.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" - market = await client.get_spot_market(market_id=market_id) + market = await client.fetch_spot_market(market_id=market_id) print(market) diff --git a/examples/exchange_client/spot_exchange_rpc/2_Markets.py b/examples/exchange_client/spot_exchange_rpc/2_Markets.py index dc37d25f..46ad1239 100644 --- a/examples/exchange_client/spot_exchange_rpc/2_Markets.py +++ b/examples/exchange_client/spot_exchange_rpc/2_Markets.py @@ -10,7 +10,9 @@ async def main() -> None: market_status = "active" base_denom = "inj" quote_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" - market = await client.get_spot_markets(market_status=market_status, base_denom=base_denom, quote_denom=quote_denom) + market = await client.fetch_spot_markets( + market_status=market_status, base_denom=base_denom, quote_denom=quote_denom + ) print(market) diff --git a/examples/exchange_client/spot_exchange_rpc/4_Orderbook.py b/examples/exchange_client/spot_exchange_rpc/4_Orderbook.py index 1de0c539..9e4e08eb 100644 --- a/examples/exchange_client/spot_exchange_rpc/4_Orderbook.py +++ b/examples/exchange_client/spot_exchange_rpc/4_Orderbook.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" - orderbook = await client.get_spot_orderbookV2(market_id=market_id) + orderbook = await client.fetch_spot_orderbook_v2(market_id=market_id) print(orderbook) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index c9c4d4fd..c11e3d42 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -18,6 +18,7 @@ from pyinjective.client.indexer.grpc.indexer_grpc_insurance_api import IndexerGrpcInsuranceApi from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi from pyinjective.client.indexer.grpc.indexer_grpc_oracle_api import IndexerGrpcOracleApi +from pyinjective.client.indexer.grpc.indexer_grpc_spot_api import IndexerGrpcSpotApi from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_auction_stream import IndexerGrpcAuctionStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream @@ -198,6 +199,12 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_spot_api = IndexerGrpcSpotApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) self.exchange_account_stream_api = IndexerGrpcAccountStream( channel=self.exchange_channel, @@ -917,10 +924,21 @@ async def fetch_redemptions( # SpotRPC async def get_spot_market(self, market_id: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_market` instead + """ + warn("This method is deprecated. Use fetch_spot_market instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.MarketRequest(market_id=market_id) return await self.stubSpotExchange.Market(req) + async def fetch_spot_market(self, market_id: str) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_market(market_id=market_id) + async def get_spot_markets(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_markets` instead + """ + warn("This method is deprecated. Use fetch_spot_markets instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.MarketsRequest( market_status=kwargs.get("market_status"), base_denom=kwargs.get("base_denom"), @@ -928,6 +946,16 @@ async def get_spot_markets(self, **kwargs): ) return await self.stubSpotExchange.Markets(req) + async def fetch_spot_markets( + self, + market_status: Optional[str] = None, + base_denom: Optional[str] = None, + quote_denom: Optional[str] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_markets( + market_status=market_status, base_denom=base_denom, quote_denom=quote_denom + ) + async def stream_spot_markets(self, **kwargs): req = spot_exchange_rpc_pb.StreamMarketsRequest(market_ids=kwargs.get("market_ids")) metadata = await self.network.exchange_metadata( @@ -936,9 +964,16 @@ async def stream_spot_markets(self, **kwargs): return self.stubSpotExchange.StreamMarkets(request=req, metadata=metadata) async def get_spot_orderbookV2(self, market_id: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_markets` instead + """ + warn("This method is deprecated. Use fetch_spot_orderbook_v2 instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.OrderbookV2Request(market_id=market_id) return await self.stubSpotExchange.OrderbookV2(req) + async def fetch_spot_orderbook_v2(self, market_id: str) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_orderbook_v2(market_id=market_id) + async def get_spot_orderbooksV2(self, market_ids: List): req = spot_exchange_rpc_pb.OrderbooksV2Request(market_ids=market_ids) return await self.stubSpotExchange.OrderbooksV2(req) @@ -1313,19 +1348,20 @@ async def _initialize_tokens_and_markets(self): binary_option_markets = dict() tokens = dict() tokens_by_denom = dict() - markets_info = (await self.get_spot_markets(market_status="active")).markets + markets_info = (await self.fetch_spot_markets(market_status="active"))["markets"] for market_info in markets_info: - if "/" in market_info.ticker: - base_token_symbol, quote_token_symbol = market_info.ticker.split(constant.TICKER_TOKENS_SEPARATOR) + ticker = market_info["ticker"] + if "/" in ticker: + base_token_symbol, quote_token_symbol = ticker.split(constant.TICKER_TOKENS_SEPARATOR) else: - base_token_symbol = market_info.base_token_meta.symbol - quote_token_symbol = market_info.quote_token_meta.symbol + base_token_symbol = market_info["baseTokenMeta"]["symbol"] + quote_token_symbol = market_info["quoteTokenMeta"]["symbol"] base_token = self._token_representation( symbol=base_token_symbol, - token_meta=market_info.base_token_meta, - denom=market_info.base_denom, + token_meta=market_info["baseTokenMeta"], + denom=market_info["baseDenom"], all_tokens=tokens, ) if base_token.denom not in tokens_by_denom: @@ -1333,81 +1369,81 @@ async def _initialize_tokens_and_markets(self): quote_token = self._token_representation( symbol=quote_token_symbol, - token_meta=market_info.quote_token_meta, - denom=market_info.quote_denom, + token_meta=market_info["quoteTokenMeta"], + denom=market_info["quoteDenom"], all_tokens=tokens, ) if quote_token.denom not in tokens_by_denom: tokens_by_denom[quote_token.denom] = quote_token market = SpotMarket( - id=market_info.market_id, - status=market_info.market_status, - ticker=market_info.ticker, + id=market_info["marketId"], + status=market_info["marketStatus"], + ticker=market_info["ticker"], base_token=base_token, quote_token=quote_token, - maker_fee_rate=Decimal(market_info.maker_fee_rate), - taker_fee_rate=Decimal(market_info.taker_fee_rate), - service_provider_fee=Decimal(market_info.service_provider_fee), - min_price_tick_size=Decimal(market_info.min_price_tick_size), - min_quantity_tick_size=Decimal(market_info.min_quantity_tick_size), + maker_fee_rate=Decimal(market_info["makerFeeRate"]), + taker_fee_rate=Decimal(market_info["takerFeeRate"]), + service_provider_fee=Decimal(market_info["serviceProviderFee"]), + min_price_tick_size=Decimal(market_info["minPriceTickSize"]), + min_quantity_tick_size=Decimal(market_info["minQuantityTickSize"]), ) spot_markets[market.id] = market markets_info = (await self.get_derivative_markets(market_status="active")).markets for market_info in markets_info: - quote_token_symbol = market_info.quote_token_meta.symbol + quote_token_symbol = market_info["quoteTokenMeta"].symbol quote_token = self._token_representation( symbol=quote_token_symbol, - token_meta=market_info.quote_token_meta, - denom=market_info.quote_denom, + token_meta=market_info["quoteTokenMeta"], + denom=market_info["quoteDenom"], all_tokens=tokens, ) if quote_token.denom not in tokens_by_denom: tokens_by_denom[quote_token.denom] = quote_token market = DerivativeMarket( - id=market_info.market_id, - status=market_info.market_status, - ticker=market_info.ticker, - oracle_base=market_info.oracle_base, - oracle_quote=market_info.oracle_quote, - oracle_type=market_info.oracle_type, - oracle_scale_factor=market_info.oracle_scale_factor, - initial_margin_ratio=Decimal(market_info.initial_margin_ratio), - maintenance_margin_ratio=Decimal(market_info.maintenance_margin_ratio), + id=market_info["marketId"], + status=market_info["marketStatus"], + ticker=market_info["ticker"], + oracle_base=market_info["oracleBase"], + oracle_quote=market_info["oracleQuote"], + oracle_type=market_info["oracleType"], + oracle_scale_factor=market_info["oracleScaleFactor"], + initial_margin_ratio=Decimal(market_info["initialMarginRatio"]), + maintenance_margin_ratio=Decimal(market_info["maintenanceMarginRatio"]), quote_token=quote_token, - maker_fee_rate=Decimal(market_info.maker_fee_rate), - taker_fee_rate=Decimal(market_info.taker_fee_rate), - service_provider_fee=Decimal(market_info.service_provider_fee), - min_price_tick_size=Decimal(market_info.min_price_tick_size), - min_quantity_tick_size=Decimal(market_info.min_quantity_tick_size), + maker_fee_rate=Decimal(market_info["makerFeeRate"]), + taker_fee_rate=Decimal(market_info["takerFeeRate"]), + service_provider_fee=Decimal(market_info["serviceProviderFee"]), + min_price_tick_size=Decimal(market_info["minPriceTickSize"]), + min_quantity_tick_size=Decimal(market_info["minQuantityTickSize"]), ) derivative_markets[market.id] = market markets_info = (await self.get_binary_options_markets(market_status="active")).markets for market_info in markets_info: - quote_token = tokens_by_denom.get(market_info.quote_denom, None) + quote_token = tokens_by_denom.get(market_info["quoteDenom"], None) market = BinaryOptionMarket( - id=market_info.market_id, - status=market_info.market_status, - ticker=market_info.ticker, - oracle_symbol=market_info.oracle_symbol, - oracle_provider=market_info.oracle_provider, - oracle_type=market_info.oracle_type, - oracle_scale_factor=market_info.oracle_scale_factor, - expiration_timestamp=market_info.expiration_timestamp, - settlement_timestamp=market_info.settlement_timestamp, + id=market_info["marketId"], + status=market_info["marketStatus"], + ticker=market_info["ticker"], + oracle_symbol=market_info["oracleSymbol"], + oracle_provider=market_info["oracleProvider"], + oracle_type=market_info["oracleType"], + oracle_scale_factor=market_info["oracleScaleFactor"], + expiration_timestamp=market_info["expirationTimestamp"], + settlement_timestamp=market_info["settlementTimestamp"], quote_token=quote_token, - maker_fee_rate=Decimal(market_info.maker_fee_rate), - taker_fee_rate=Decimal(market_info.taker_fee_rate), - service_provider_fee=Decimal(market_info.service_provider_fee), - min_price_tick_size=Decimal(market_info.min_price_tick_size), - min_quantity_tick_size=Decimal(market_info.min_quantity_tick_size), + maker_fee_rate=Decimal(market_info["makerFeeRate"]), + taker_fee_rate=Decimal(market_info["takerFeeRate"]), + service_provider_fee=Decimal(market_info["serviceProviderFee"]), + min_price_tick_size=Decimal(market_info["minPriceTickSize"]), + min_quantity_tick_size=Decimal(market_info["minQuantityTickSize"]), ) binary_option_markets[market.id] = market diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py new file mode 100644 index 00000000..5dc46bbb --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py @@ -0,0 +1,45 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_spot_exchange_rpc_pb2 as exchange_spot_pb, + injective_spot_exchange_rpc_pb2_grpc as exchange_spot_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcSpotApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_spot_grpc.InjectiveSpotExchangeRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_markets( + self, + market_status: Optional[str] = None, + base_denom: Optional[str] = None, + quote_denom: Optional[str] = None, + ) -> Dict[str, Any]: + request = exchange_spot_pb.MarketsRequest( + market_status=market_status, + base_denom=base_denom, + quote_denom=quote_denom, + ) + response = await self._execute_call(call=self._stub.Markets, request=request) + + return response + + async def fetch_market(self, market_id: str) -> Dict[str, Any]: + request = exchange_spot_pb.MarketRequest(market_id=market_id) + response = await self._execute_call(call=self._stub.Market, request=request) + + return response + + async def fetch_orderbook_v2(self, market_id: str) -> Dict[str, Any]: + request = exchange_spot_pb.OrderbookV2Request(market_id=market_id) + response = await self._execute_call(call=self._stub.OrderbookV2, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/utils/fetch_metadata.py b/pyinjective/utils/fetch_metadata.py index 715e43c5..68b52989 100644 --- a/pyinjective/utils/fetch_metadata.py +++ b/pyinjective/utils/fetch_metadata.py @@ -31,30 +31,32 @@ async def fetch_denom(network) -> str: # fetch meta data for spot markets client = AsyncClient(network) status = "active" - mresp = await client.get_spot_markets(market_status=status) - for market in mresp.markets: + mresp = await client.fetch_spot_markets(market_status=status) + for market in mresp["markets"]: # append symbols to dict - if market.base_token_meta.SerializeToString() != "": - symbols[market.base_token_meta.symbol] = (market.base_denom, market.base_token_meta.decimals) + if market["baseTokenMeta"] != "": + symbols[market["baseTokenMeta"]["symbol"]] = (market["baseDenom"], market["baseTokenMeta"]["decimals"]) - if market.quote_token_meta.SerializeToString() != "": - symbols[market.quote_token_meta.symbol] = (market.base_denom, market.quote_token_meta.decimals) + if market["quoteTokenMeta"] != "": + symbols[market["quoteTokenMeta"]["symbol"]] = (market["baseDenom"], market["quoteTokenMeta"]["decimals"]) # format into ini entry - min_display_price_tick_size = float(market.min_price_tick_size) / pow( - 10, market.quote_token_meta.decimals - market.base_token_meta.decimals + min_display_price_tick_size = float(market["minPriceTickSize"]) / pow( + 10, market["quoteTokenMeta"]["decimals"] - market["baseTokenMeta"]["decimals"] + ) + min_display_quantity_tick_size = float(market["minQuantityTickSize"]) / pow( + 10, market["baseTokenMeta"]["decimals"] ) - min_display_quantity_tick_size = float(market.min_quantity_tick_size) / pow(10, market.base_token_meta.decimals) config = metadata_template.format( - market.market_id, + market["marketId"], network.string().capitalize(), "Spot", - market.ticker, - market.base_token_meta.decimals, - market.quote_token_meta.decimals, - market.min_price_tick_size, + market["ticker"], + market["baseTokenMeta"]["decimals"], + market["quoteTokenMeta"]["decimals"], + market["minPriceTickSize"], min_display_price_tick_size, - market.min_quantity_tick_size, + market["minQuantityTickSize"], min_display_quantity_tick_size, ) denom_output += config @@ -65,22 +67,22 @@ async def fetch_denom(network) -> str: mresp = await client.get_derivative_markets(market_status=status) for market in mresp.markets: # append symbols to dict - if market.quote_token_meta.SerializeToString() != "": - symbols[market.quote_token_meta.symbol] = (market.quote_denom, market.quote_token_meta.decimals) + if market["quoteTokenMeta"] != "": + symbols[market["quoteTokenMeta"]["symbol"]] = (market["quoteDenom"], market["quoteTokenMeta"]["decimals"]) # format into ini entry - min_display_price_tick_size = float(market.min_price_tick_size) / pow(10, market.quote_token_meta.decimals) + min_display_price_tick_size = float(market["minPriceTickSize"]) / pow(10, market["quoteTokenMeta"]["decimals"]) config = metadata_template.format( - market.market_id, + market["marketId"], network.string().capitalize(), "Derivative", - market.ticker, + market["ticker"], 0, - market.quote_token_meta.decimals, - market.min_price_tick_size, + market["quoteTokenMeta"]["decimals"], + market["minPriceTickSize"], min_display_price_tick_size, - market.min_quantity_tick_size, - market.min_quantity_tick_size, + market["minQuantityTickSize"], + market["minQuantityTickSize"], ) denom_output += config diff --git a/tests/client/indexer/configurable_spot_query_servicer.py b/tests/client/indexer/configurable_spot_query_servicer.py new file mode 100644 index 00000000..4acd2661 --- /dev/null +++ b/tests/client/indexer/configurable_spot_query_servicer.py @@ -0,0 +1,23 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_spot_exchange_rpc_pb2 as exchange_spot_pb, + injective_spot_exchange_rpc_pb2_grpc as exchange_spot_grpc, +) + + +class ConfigurableSpotQueryServicer(exchange_spot_grpc.InjectiveSpotExchangeRPCServicer): + def __init__(self): + super().__init__() + self.markets_responses = deque() + self.market_responses = deque() + self.orderbook_v2_responses = deque() + + async def Markets(self, request: exchange_spot_pb.MarketsRequest, context=None, metadata=None): + return self.markets_responses.pop() + + async def Market(self, request: exchange_spot_pb.MarketRequest, context=None, metadata=None): + return self.market_responses.pop() + + async def OrderbookV2(self, request: exchange_spot_pb.OrderbookV2Request, context=None, metadata=None): + return self.orderbook_v2_responses.pop() diff --git a/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py b/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py new file mode 100644 index 00000000..1b34b18d --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py @@ -0,0 +1,250 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_spot_api import IndexerGrpcSpotApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_spot_exchange_rpc_pb2 as exchange_spot_pb +from tests.client.indexer.configurable_spot_query_servicer import ConfigurableSpotQueryServicer + + +@pytest.fixture +def spot_servicer(): + return ConfigurableSpotQueryServicer() + + +class TestIndexerGrpcSpotApi: + @pytest.mark.asyncio + async def test_fetch_markets( + self, + spot_servicer, + ): + base_token_meta = exchange_spot_pb.TokenMeta( + name="Injective Protocol", + address="0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", + symbol="INJ", + logo="https://static.alchemyapi.io/images/assets/7226.png", + decimals=18, + updated_at=1683119359318, + ) + quote_token_meta = exchange_spot_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + + market = exchange_spot_pb.SpotMarketInfo( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + market_status="active", + ticker="INJ/USDT", + base_denom="inj", + base_token_meta=base_token_meta, + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + min_price_tick_size="0.000000000000001", + min_quantity_tick_size="1000000000000000", + ) + + spot_servicer.markets_responses.append( + exchange_spot_pb.MarketsResponse( + markets=[market], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_markets = await api.fetch_markets( + market_status=market.market_status, + base_denom=market.base_denom, + quote_denom=market.quote_denom, + ) + expected_markets = { + "markets": [ + { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "baseDenom": market.base_denom, + "baseTokenMeta": { + "name": market.base_token_meta.name, + "address": market.base_token_meta.address, + "symbol": market.base_token_meta.symbol, + "logo": market.base_token_meta.logo, + "decimals": market.base_token_meta.decimals, + "updatedAt": str(market.base_token_meta.updated_at), + }, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "takerFeeRate": market.taker_fee_rate, + "makerFeeRate": market.maker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + } + ] + } + + assert result_markets == expected_markets + + @pytest.mark.asyncio + async def test_fetch_market( + self, + spot_servicer, + ): + base_token_meta = exchange_spot_pb.TokenMeta( + name="Injective Protocol", + address="0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", + symbol="INJ", + logo="https://static.alchemyapi.io/images/assets/7226.png", + decimals=18, + updated_at=1683119359318, + ) + quote_token_meta = exchange_spot_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + + market = exchange_spot_pb.SpotMarketInfo( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + market_status="active", + ticker="INJ/USDT", + base_denom="inj", + base_token_meta=base_token_meta, + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + min_price_tick_size="0.000000000000001", + min_quantity_tick_size="1000000000000000", + ) + + spot_servicer.market_responses.append( + exchange_spot_pb.MarketResponse( + market=market, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_market = await api.fetch_market(market_id=market.market_id) + expected_market = { + "market": { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "baseDenom": market.base_denom, + "baseTokenMeta": { + "name": market.base_token_meta.name, + "address": market.base_token_meta.address, + "symbol": market.base_token_meta.symbol, + "logo": market.base_token_meta.logo, + "decimals": market.base_token_meta.decimals, + "updatedAt": str(market.base_token_meta.updated_at), + }, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "takerFeeRate": market.taker_fee_rate, + "makerFeeRate": market.maker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + } + } + + assert result_market == expected_market + + @pytest.mark.asyncio + async def test_fetch_orderbook_v2( + self, + spot_servicer, + ): + buy = exchange_spot_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_spot_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_spot_pb.SpotLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + spot_servicer.orderbook_v2_responses.append( + exchange_spot_pb.OrderbookV2Response( + orderbook=orderbook, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_orderbook = await api.fetch_orderbook_v2( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + ) + expected_orderbook = { + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + } + } + + assert result_orderbook == expected_orderbook + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 4fda5760..22892c61 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -13,6 +13,7 @@ injective_insurance_rpc_pb2 as exchange_insurance_pb, injective_meta_rpc_pb2 as exchange_meta_pb, injective_oracle_rpc_pb2 as exchange_oracle_pb, + injective_spot_exchange_rpc_pb2 as exchange_spot_pb, ) from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb from tests.client.chain.grpc.configurable_auth_query_servicer import ConfigurableAuthQueryServicer @@ -23,6 +24,7 @@ from tests.client.indexer.configurable_insurance_query_servicer import ConfigurableInsuranceQueryServicer from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer +from tests.client.indexer.configurable_spot_query_servicer import ConfigurableSpotQueryServicer from tests.core.tx.grpc.configurable_tx_query_servicer import ConfigurableTxQueryServicer @@ -66,6 +68,11 @@ def oracle_servicer(): return ConfigurableOracleQueryServicer() +@pytest.fixture +def spot_servicer(): + return ConfigurableSpotQueryServicer() + + @pytest.fixture def tx_servicer(): return ConfigurableTxQueryServicer() @@ -662,3 +669,57 @@ async def test_stream_bids_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use listen_bids_updates instead" + + @pytest.mark.asyncio + async def test_get_spot_markets_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.markets_responses.append(exchange_spot_pb.MarketsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_markets() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_markets instead" + + @pytest.mark.asyncio + async def test_get_spot_market_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.market_responses.append(exchange_spot_pb.MarketResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_market(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_market instead" + + @pytest.mark.asyncio + async def test_get_spot_orderbookV2_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.orderbook_v2_responses.append(exchange_spot_pb.OrderbookV2Response()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_orderbookV2(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_orderbook_v2 instead" From c7c1b53dbbc0d8e2de7094b1ff62b09edbd235e1 Mon Sep 17 00:00:00 2001 From: abel Date: Mon, 13 Nov 2023 09:35:12 -0300 Subject: [PATCH 17/27] (feat) Implemented request endpoints for spot exchange in the low level API component. Added new functions in AsyncClient and deprecated the old ones. Updated examples --- .../11_SubaccountOrdersList.py | 6 +- .../12_SubaccountTradesList.py | 7 +- .../spot_exchange_rpc/14_Orderbooks.py | 2 +- .../spot_exchange_rpc/15_HistoricalOrders.py | 11 +- .../spot_exchange_rpc/2_Markets.py | 2 +- .../spot_exchange_rpc/6_Trades.py | 6 +- pyinjective/async_client.py | 137 ++++- .../indexer/grpc/indexer_grpc_spot_api.py | 149 +++++- pyinjective/client/model/pagination.py | 12 +- .../chain/grpc/test_chain_grpc_auth_api.py | 2 +- .../chain/grpc/test_chain_grpc_authz_api.py | 6 +- .../configurable_spot_query_servicer.py | 28 + .../grpc/test_indexer_grpc_spot_api.py | 483 +++++++++++++++++- .../test_async_client_deprecation_warnings.py | 112 ++++ 14 files changed, 934 insertions(+), 29 deletions(-) diff --git a/examples/exchange_client/spot_exchange_rpc/11_SubaccountOrdersList.py b/examples/exchange_client/spot_exchange_rpc/11_SubaccountOrdersList.py index 03e901e9..760119dc 100644 --- a/examples/exchange_client/spot_exchange_rpc/11_SubaccountOrdersList.py +++ b/examples/exchange_client/spot_exchange_rpc/11_SubaccountOrdersList.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -12,8 +13,9 @@ async def main() -> None: market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" skip = 10 limit = 10 - orders = await client.get_spot_subaccount_orders( - subaccount_id=subaccount_id, market_id=market_id, skip=skip, limit=limit + pagination = PaginationOption(skip=skip, limit=limit) + orders = await client.fetch_spot_subaccount_orders_list( + subaccount_id=subaccount_id, market_id=market_id, pagination=pagination ) print(orders) diff --git a/examples/exchange_client/spot_exchange_rpc/12_SubaccountTradesList.py b/examples/exchange_client/spot_exchange_rpc/12_SubaccountTradesList.py index a9670e58..d35a12e9 100644 --- a/examples/exchange_client/spot_exchange_rpc/12_SubaccountTradesList.py +++ b/examples/exchange_client/spot_exchange_rpc/12_SubaccountTradesList.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -14,13 +15,13 @@ async def main() -> None: direction = "buy" skip = 2 limit = 3 - trades = await client.get_spot_subaccount_trades( + pagination = PaginationOption(skip=skip, limit=limit) + trades = await client.fetch_spot_subaccount_trades_list( subaccount_id=subaccount_id, market_id=market_id, execution_type=execution_type, direction=direction, - skip=skip, - limit=limit, + pagination=pagination, ) print(trades) diff --git a/examples/exchange_client/spot_exchange_rpc/14_Orderbooks.py b/examples/exchange_client/spot_exchange_rpc/14_Orderbooks.py index 2a99f818..ac672940 100644 --- a/examples/exchange_client/spot_exchange_rpc/14_Orderbooks.py +++ b/examples/exchange_client/spot_exchange_rpc/14_Orderbooks.py @@ -11,7 +11,7 @@ async def main() -> None: "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", "0x7a57e705bb4e09c88aecfc295569481dbf2fe1d5efe364651fbe72385938e9b0", ] - orderbooks = await client.get_spot_orderbooksV2(market_ids=market_ids) + orderbooks = await client.fetch_spot_orderbooks_v2(market_ids=market_ids) print(orderbooks) diff --git a/examples/exchange_client/spot_exchange_rpc/15_HistoricalOrders.py b/examples/exchange_client/spot_exchange_rpc/15_HistoricalOrders.py index 0a39f80d..bac4b2c1 100644 --- a/examples/exchange_client/spot_exchange_rpc/15_HistoricalOrders.py +++ b/examples/exchange_client/spot_exchange_rpc/15_HistoricalOrders.py @@ -1,19 +1,24 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network async def main() -> None: network = Network.testnet() client = AsyncClient(network) - market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + market_ids = ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"] subaccount_id = "0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000" skip = 10 limit = 3 order_types = ["buy_po"] - orders = await client.get_historical_spot_orders( - market_id=market_id, subaccount_id=subaccount_id, skip=skip, limit=limit, order_types=order_types + pagination = PaginationOption(skip=skip, limit=limit) + orders = await client.fetch_spot_orders_history( + subaccount_id=subaccount_id, + market_ids=market_ids, + order_types=order_types, + pagination=pagination, ) print(orders) diff --git a/examples/exchange_client/spot_exchange_rpc/2_Markets.py b/examples/exchange_client/spot_exchange_rpc/2_Markets.py index 46ad1239..3dc815eb 100644 --- a/examples/exchange_client/spot_exchange_rpc/2_Markets.py +++ b/examples/exchange_client/spot_exchange_rpc/2_Markets.py @@ -11,7 +11,7 @@ async def main() -> None: base_denom = "inj" quote_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" market = await client.fetch_spot_markets( - market_status=market_status, base_denom=base_denom, quote_denom=quote_denom + market_statuses=[market_status], base_denom=base_denom, quote_denom=quote_denom ) print(market) diff --git a/examples/exchange_client/spot_exchange_rpc/6_Trades.py b/examples/exchange_client/spot_exchange_rpc/6_Trades.py index 6c035553..029a4f90 100644 --- a/examples/exchange_client/spot_exchange_rpc/6_Trades.py +++ b/examples/exchange_client/spot_exchange_rpc/6_Trades.py @@ -10,13 +10,13 @@ async def main() -> None: market_ids = ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"] execution_side = "taker" direction = "buy" - subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001" + subaccount_ids = ["0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001"] execution_types = ["limitMatchNewOrder", "market"] - orders = await client.get_spot_trades( + orders = await client.fetch_spot_trades( market_ids=market_ids, + subaccount_ids=subaccount_ids, execution_side=execution_side, direction=direction, - subaccount_id=subaccount_id, execution_types=execution_types, ) print(orders) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 41e7bc3b..a1bc3fc0 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -953,12 +953,12 @@ async def get_spot_markets(self, **kwargs): async def fetch_spot_markets( self, - market_status: Optional[str] = None, + market_statuses: Optional[List[str]] = None, base_denom: Optional[str] = None, quote_denom: Optional[str] = None, ) -> Dict[str, Any]: return await self.exchange_spot_api.fetch_markets( - market_status=market_status, base_denom=base_denom, quote_denom=quote_denom + market_statuses=market_statuses, base_denom=base_denom, quote_denom=quote_denom ) async def stream_spot_markets(self, **kwargs): @@ -970,7 +970,7 @@ async def stream_spot_markets(self, **kwargs): async def get_spot_orderbookV2(self, market_id: str): """ - This method is deprecated and will be removed soon. Please use `fetch_spot_markets` instead + This method is deprecated and will be removed soon. Please use `fetch_spot_orderbook_v2` instead """ warn("This method is deprecated. Use fetch_spot_orderbook_v2 instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.OrderbookV2Request(market_id=market_id) @@ -980,10 +980,21 @@ async def fetch_spot_orderbook_v2(self, market_id: str) -> Dict[str, Any]: return await self.exchange_spot_api.fetch_orderbook_v2(market_id=market_id) async def get_spot_orderbooksV2(self, market_ids: List): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_orderbooks_v2` instead + """ + warn("This method is deprecated. Use fetch_spot_orderbooks_v2 instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.OrderbooksV2Request(market_ids=market_ids) return await self.stubSpotExchange.OrderbooksV2(req) + async def fetch_spot_orderbooks_v2(self, market_ids: List[str]) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_orderbooks_v2(market_ids=market_ids) + async def get_spot_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_orders` instead + """ + warn("This method is deprecated. Use fetch_spot_orders instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.OrdersRequest( market_id=market_id, order_side=kwargs.get("order_side"), @@ -1000,7 +1011,33 @@ async def get_spot_orders(self, market_id: str, **kwargs): ) return await self.stubSpotExchange.Orders(req) + async def fetch_spot_orders( + self, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_orders( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + pagination=pagination, + ) + async def get_historical_spot_orders(self, market_id: Optional[str] = None, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_orders_history` instead + """ + warn("This method is deprecated. Use fetch_spot_orders_history instead", DeprecationWarning, stacklevel=2) market_ids = kwargs.get("market_ids", []) if market_id is not None: market_ids.append(market_id) @@ -1025,7 +1062,37 @@ async def get_historical_spot_orders(self, market_id: Optional[str] = None, **kw ) return await self.stubSpotExchange.OrdersHistory(req) + async def fetch_spot_orders_history( + self, + subaccount_id: Optional[str] = None, + market_ids: Optional[List[str]] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + active_markets_only: Optional[bool] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_orders_history( + subaccount_id=subaccount_id, + market_ids=market_ids, + order_types=order_types, + direction=direction, + state=state, + execution_types=execution_types, + trade_id=trade_id, + active_markets_only=active_markets_only, + cid=cid, + pagination=pagination, + ) + async def get_spot_trades(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_trades` instead + """ + warn("This method is deprecated. Use fetch_spot_trades instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.TradesRequest( market_id=kwargs.get("market_id"), execution_side=kwargs.get("execution_side"), @@ -1044,6 +1111,30 @@ async def get_spot_trades(self, **kwargs): ) return await self.stubSpotExchange.Trades(req) + async def fetch_spot_trades( + self, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_trades( + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + pagination=pagination, + ) + async def stream_spot_orderbook_snapshot(self, market_ids: List[str]): req = spot_exchange_rpc_pb.StreamOrderbookV2Request(market_ids=market_ids) metadata = await self.network.exchange_metadata( @@ -1129,6 +1220,12 @@ async def stream_spot_trades(self, **kwargs): return self.stubSpotExchange.StreamTrades(request=req, metadata=metadata) async def get_spot_subaccount_orders(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_subaccount_orders_list` instead + """ + warn( + "This method is deprecated. Use fetch_spot_subaccount_orders_list instead", DeprecationWarning, stacklevel=2 + ) req = spot_exchange_rpc_pb.SubaccountOrdersListRequest( subaccount_id=subaccount_id, market_id=kwargs.get("market_id"), @@ -1137,7 +1234,23 @@ async def get_spot_subaccount_orders(self, subaccount_id: str, **kwargs): ) return await self.stubSpotExchange.SubaccountOrdersList(req) + async def fetch_spot_subaccount_orders_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_subaccount_orders_list( + subaccount_id=subaccount_id, market_id=market_id, pagination=pagination + ) + async def get_spot_subaccount_trades(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_subaccount_trades_list` instead + """ + warn( + "This method is deprecated. Use fetch_spot_subaccount_trades_list instead", DeprecationWarning, stacklevel=2 + ) req = spot_exchange_rpc_pb.SubaccountTradesListRequest( subaccount_id=subaccount_id, market_id=kwargs.get("market_id"), @@ -1148,6 +1261,22 @@ async def get_spot_subaccount_trades(self, subaccount_id: str, **kwargs): ) return await self.stubSpotExchange.SubaccountTradesList(req) + async def fetch_spot_subaccount_trades_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + execution_type: Optional[str] = None, + direction: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_subaccount_trades_list( + subaccount_id=subaccount_id, + market_id=market_id, + execution_type=execution_type, + direction=direction, + pagination=pagination, + ) + # DerivativeRPC async def get_derivative_market(self, market_id: str): @@ -1445,7 +1574,7 @@ async def _initialize_tokens_and_markets(self): binary_option_markets = dict() tokens = dict() tokens_by_denom = dict() - markets_info = (await self.fetch_spot_markets(market_status="active"))["markets"] + markets_info = (await self.fetch_spot_markets(market_statuses=["active"]))["markets"] valid_markets = ( market_info for market_info in markets_info diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py index 5dc46bbb..7f3f737a 100644 --- a/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py +++ b/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py @@ -1,7 +1,8 @@ -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Dict, List, Optional from grpc.aio import Channel +from pyinjective.client.model.pagination import PaginationOption from pyinjective.proto.exchange import ( injective_spot_exchange_rpc_pb2 as exchange_spot_pb, injective_spot_exchange_rpc_pb2_grpc as exchange_spot_grpc, @@ -16,12 +17,12 @@ def __init__(self, channel: Channel, metadata_provider: Callable): async def fetch_markets( self, - market_status: Optional[str] = None, + market_statuses: Optional[List[str]] = None, base_denom: Optional[str] = None, quote_denom: Optional[str] = None, ) -> Dict[str, Any]: request = exchange_spot_pb.MarketsRequest( - market_status=market_status, + market_statuses=market_statuses, base_denom=base_denom, quote_denom=quote_denom, ) @@ -41,5 +42,147 @@ async def fetch_orderbook_v2(self, market_id: str) -> Dict[str, Any]: return response + async def fetch_orderbooks_v2(self, market_ids: List[str]) -> Dict[str, Any]: + request = exchange_spot_pb.OrderbooksV2Request(market_ids=market_ids) + response = await self._execute_call(call=self._stub.OrderbooksV2, request=request) + + return response + + async def fetch_orders( + self, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.OrdersRequest( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.Orders, request=request) + + return response + + async def fetch_trades( + self, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.TradesRequest( + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.Trades, request=request) + + return response + + async def fetch_subaccount_orders_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.SubaccountOrdersListRequest( + subaccount_id=subaccount_id, + market_id=market_id, + skip=pagination.skip, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.SubaccountOrdersList, request=request) + + return response + + async def fetch_subaccount_trades_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + execution_type: Optional[str] = None, + direction: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.SubaccountTradesListRequest( + subaccount_id=subaccount_id, + market_id=market_id, + execution_type=execution_type, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.SubaccountTradesList, request=request) + + return response + + async def fetch_orders_history( + self, + subaccount_id: Optional[str] = None, + market_ids: Optional[List[str]] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + active_markets_only: Optional[bool] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.OrdersHistoryRequest( + subaccount_id=subaccount_id, + market_ids=market_ids, + skip=pagination.skip, + limit=pagination.limit, + order_types=order_types, + direction=direction, + start_time=pagination.start_time, + end_time=pagination.end_time, + state=state, + execution_types=execution_types, + trade_id=trade_id, + active_markets_only=active_markets_only, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.OrdersHistory, request=request) + + return response + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/model/pagination.py b/pyinjective/client/model/pagination.py index ae37d6ec..bab737db 100644 --- a/pyinjective/client/model/pagination.py +++ b/pyinjective/client/model/pagination.py @@ -7,15 +7,19 @@ class PaginationOption: def __init__( self, key: Optional[str] = None, - offset: Optional[int] = None, + skip: Optional[int] = None, limit: Optional[int] = None, + start_time: Optional[int] = None, + end_time: Optional[int] = None, reverse: Optional[bool] = None, count_total: Optional[bool] = None, ): super().__init__() self.key = key - self.offset = offset + self.skip = skip self.limit = limit + self.start_time = start_time + self.end_time = end_time self.reverse = reverse self.count_total = count_total @@ -24,8 +28,8 @@ def create_pagination_request(self) -> pagination_pb.PageRequest: if self.key is not None: page_request.key = bytes.fromhex(self.key) - if self.offset is not None: - page_request.offset = self.offset + if self.skip is not None: + page_request.offset = self.skip if self.limit is not None: page_request.limit = self.limit if self.reverse is not None: diff --git a/tests/client/chain/grpc/test_chain_grpc_auth_api.py b/tests/client/chain/grpc/test_chain_grpc_auth_api.py index 24db74a4..03032cb1 100644 --- a/tests/client/chain/grpc/test_chain_grpc_auth_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_auth_api.py @@ -146,7 +146,7 @@ async def test_fetch_accounts( pagination_option = PaginationOption( key="011ab4075a94245dff7338e3042db5b7cc3f42e1", - offset=10, + skip=10, limit=30, reverse=False, count_total=True, diff --git a/tests/client/chain/grpc/test_chain_grpc_authz_api.py b/tests/client/chain/grpc/test_chain_grpc_authz_api.py index b728a015..e097ff66 100644 --- a/tests/client/chain/grpc/test_chain_grpc_authz_api.py +++ b/tests/client/chain/grpc/test_chain_grpc_authz_api.py @@ -41,7 +41,7 @@ async def test_fetch_grants( ) pagination_option = PaginationOption( - offset=10, + skip=10, limit=30, reverse=False, count_total=True, @@ -103,7 +103,7 @@ async def test_fetch_granter_grants( ) pagination_option = PaginationOption( - offset=10, + skip=10, limit=30, reverse=False, count_total=True, @@ -165,7 +165,7 @@ async def test_fetch_grantee_grants( ) pagination_option = PaginationOption( - offset=10, + skip=10, limit=30, reverse=False, count_total=True, diff --git a/tests/client/indexer/configurable_spot_query_servicer.py b/tests/client/indexer/configurable_spot_query_servicer.py index 4acd2661..cdfbc336 100644 --- a/tests/client/indexer/configurable_spot_query_servicer.py +++ b/tests/client/indexer/configurable_spot_query_servicer.py @@ -12,6 +12,12 @@ def __init__(self): self.markets_responses = deque() self.market_responses = deque() self.orderbook_v2_responses = deque() + self.orderbooks_v2_responses = deque() + self.orders_responses = deque() + self.trades_responses = deque() + self.subaccount_orders_list_responses = deque() + self.subaccount_trades_list_responses = deque() + self.orders_history_responses = deque() async def Markets(self, request: exchange_spot_pb.MarketsRequest, context=None, metadata=None): return self.markets_responses.pop() @@ -21,3 +27,25 @@ async def Market(self, request: exchange_spot_pb.MarketRequest, context=None, me async def OrderbookV2(self, request: exchange_spot_pb.OrderbookV2Request, context=None, metadata=None): return self.orderbook_v2_responses.pop() + + async def OrderbooksV2(self, request: exchange_spot_pb.OrderbooksV2Request, context=None, metadata=None): + return self.orderbooks_v2_responses.pop() + + async def Orders(self, request: exchange_spot_pb.OrdersRequest, context=None, metadata=None): + return self.orders_responses.pop() + + async def Trades(self, request: exchange_spot_pb.TradesRequest, context=None, metadata=None): + return self.trades_responses.pop() + + async def SubaccountOrdersList( + self, request: exchange_spot_pb.SubaccountOrdersListRequest, context=None, metadata=None + ): + return self.subaccount_orders_list_responses.pop() + + async def SubaccountTradesList( + self, request: exchange_spot_pb.SubaccountTradesListRequest, context=None, metadata=None + ): + return self.subaccount_trades_list_responses.pop() + + async def OrdersHistory(self, request: exchange_spot_pb.OrdersHistoryRequest, context=None, metadata=None): + return self.orders_history_responses.pop() diff --git a/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py b/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py index 1b34b18d..4dad0933 100644 --- a/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py +++ b/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py @@ -2,6 +2,7 @@ import pytest from pyinjective.client.indexer.grpc.indexer_grpc_spot_api import IndexerGrpcSpotApi +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network from pyinjective.proto.exchange import injective_spot_exchange_rpc_pb2 as exchange_spot_pb from tests.client.indexer.configurable_spot_query_servicer import ConfigurableSpotQueryServicer @@ -63,7 +64,7 @@ async def test_fetch_markets( api._stub = spot_servicer result_markets = await api.fetch_markets( - market_status=market.market_status, + market_statuses=[market.market_status], base_denom=market.base_denom, quote_denom=market.quote_denom, ) @@ -246,5 +247,485 @@ async def test_fetch_orderbook_v2( assert result_orderbook == expected_orderbook + @pytest.mark.asyncio + async def test_fetch_orderbooks_v2( + self, + spot_servicer, + ): + buy = exchange_spot_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_spot_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_spot_pb.SpotLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + single_orderbook = exchange_spot_pb.SingleSpotLimitOrderbookV2( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + orderbook=orderbook, + ) + + spot_servicer.orderbooks_v2_responses.append( + exchange_spot_pb.OrderbooksV2Response( + orderbooks=[single_orderbook], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_orderbook = await api.fetch_orderbooks_v2(market_ids=[single_orderbook.market_id]) + expected_orderbook = { + "orderbooks": [ + { + "marketId": single_orderbook.market_id, + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + }, + } + ] + } + + assert result_orderbook == expected_orderbook + + @pytest.mark.asyncio + async def test_fetch_orders( + self, + spot_servicer, + ): + order = exchange_spot_pb.SpotLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_spot_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + spot_servicer.orders_responses.append( + exchange_spot_pb.OrdersResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_orders = await api.fetch_orders( + market_ids=[order.market_id], + order_side=order.order_side, + subaccount_id=order.subaccount_id, + include_inactive=True, + subaccount_total_orders=True, + trade_id="7959737_3_0", + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "txHash": order.tx_hash, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_trades( + self, + spot_servicer, + ): + price = exchange_spot_pb.PriceLevel( + price="0.000000000006024", + quantity="10000000000000000", + timestamp=1677563766350, + ) + + trade = exchange_spot_pb.SpotTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + trade_execution_type="limitMatchNewOrder", + trade_direction="buy", + price=price, + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + paging = exchange_spot_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + spot_servicer.trades_responses.append( + exchange_spot_pb.TradesResponse( + trades=[trade], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_trades = await api.fetch_trades( + market_ids=[trade.market_id], + subaccount_ids=[trade.subaccount_id], + execution_side=trade.execution_side, + direction=trade.trade_direction, + execution_types=[trade.trade_execution_type], + trade_id=trade.trade_id, + account_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + cid=trade.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_trades = { + "trades": [ + { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "tradeDirection": trade.trade_direction, + "price": { + "price": price.price, + "quantity": price.quantity, + "timestamp": str(price.timestamp), + }, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_trades == expected_trades + + @pytest.mark.asyncio + async def test_fetch_subaccount_orders_list( + self, + spot_servicer, + ): + order = exchange_spot_pb.SpotLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_spot_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + spot_servicer.subaccount_orders_list_responses.append( + exchange_spot_pb.SubaccountOrdersListResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_orders = await api.fetch_subaccount_orders_list( + subaccount_id=order.subaccount_id, + market_id=order.market_id, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "txHash": order.tx_hash, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_subaccount_trades_list( + self, + spot_servicer, + ): + price = exchange_spot_pb.PriceLevel( + price="0.000000000006024", + quantity="10000000000000000", + timestamp=1677563766350, + ) + + trade = exchange_spot_pb.SpotTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + trade_execution_type="limitMatchNewOrder", + trade_direction="buy", + price=price, + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + spot_servicer.subaccount_trades_list_responses.append( + exchange_spot_pb.SubaccountTradesListResponse( + trades=[trade], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_trades = await api.fetch_subaccount_trades_list( + subaccount_id=trade.subaccount_id, + market_id=trade.market_id, + execution_type=trade.trade_execution_type, + direction=trade.trade_direction, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_trades = { + "trades": [ + { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "tradeDirection": trade.trade_direction, + "price": { + "price": price.price, + "quantity": price.quantity, + "timestamp": str(price.timestamp), + }, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + ], + } + + assert result_trades == expected_trades + + @pytest.mark.asyncio + async def test_fetch_orders_history( + self, + spot_servicer, + ): + order = exchange_spot_pb.SpotOrderHistory( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + is_active=True, + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + execution_type="limit", + order_type="buy_po", + price="0.000000000017541", + trigger_price="0", + quantity="50955000000000000000", + filled_quantity="1000000000000000", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + direction="buy", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_spot_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + spot_servicer.orders_history_responses.append( + exchange_spot_pb.OrdersHistoryResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_orders = await api.fetch_orders_history( + subaccount_id=order.subaccount_id, + market_ids=[order.market_id], + order_types=[order.order_type], + direction=order.direction, + state=order.state, + execution_types=[order.execution_type], + trade_id="8662464_1_0", + active_markets_only=True, + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "executionType": order.execution_type, + "orderType": order.order_type, + "price": order.price, + "triggerPrice": order.trigger_price, + "quantity": order.quantity, + "filledQuantity": order.filled_quantity, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "direction": order.direction, + "txHash": order.tx_hash, + "isActive": order.is_active, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + async def _dummy_metadata_provider(self): return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 22892c61..fe10273a 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -723,3 +723,115 @@ async def test_get_spot_orderbookV2_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_orderbook_v2 instead" + + @pytest.mark.asyncio + async def test_get_spot_orderbooksV2_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.orderbooks_v2_responses.append(exchange_spot_pb.OrderbooksV2Response()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_orderbooksV2(market_ids=[]) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_orderbooks_v2 instead" + + @pytest.mark.asyncio + async def test_get_spot_orders_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.orders_responses.append(exchange_spot_pb.OrdersResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_orders(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_orders instead" + + @pytest.mark.asyncio + async def test_get_spot_trades_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.trades_responses.append(exchange_spot_pb.TradesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_trades() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_trades instead" + + @pytest.mark.asyncio + async def test_get_spot_subaccount_orders_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.subaccount_orders_list_responses.append(exchange_spot_pb.SubaccountOrdersListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_subaccount_orders(subaccount_id="", market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_subaccount_orders_list instead" + ) + + @pytest.mark.asyncio + async def test_get_spot_subaccount_trades_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.subaccount_trades_list_responses.append(exchange_spot_pb.SubaccountTradesListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_subaccount_trades(subaccount_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_subaccount_trades_list instead" + ) + + @pytest.mark.asyncio + async def test_get_historical_spot_orders_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.subaccount_trades_list_responses.append(exchange_spot_pb.SubaccountTradesListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_historical_spot_orders() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_orders_history instead" From 675d71f50fbbab7133daf9bd0a4f4fd27a88424e Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 15 Nov 2023 00:35:36 -0300 Subject: [PATCH 18/27] (feat) Implemented low level API for all stream endpoints of the spot exchange module. Deprecated the old stream functions in AsyncClient and implemented new functions for the API component streams. Updated the example scripts. --- .../spot_exchange_rpc/10_StreamTrades.py | 38 +- .../spot_exchange_rpc/13_StreamOrderbooks.py | 20 - .../spot_exchange_rpc/3_StreamMarkets.py | 31 +- .../7_StreamOrderbookSnapshot.py | 36 +- .../8_StreamOrderbookUpdate.py | 166 ++--- .../9_StreamHistoricalOrders.py | 33 +- pyinjective/async_client.py | 160 +++++ .../indexer/grpc/indexer_grpc_spot_api.py | 20 + .../grpc_stream/indexer_grpc_spot_stream.py | 174 ++++++ pyinjective/client/model/pagination.py | 4 + .../utils/grpc_api_stream_assistant.py | 2 + .../configurable_spot_query_servicer.py | 39 ++ .../grpc/test_indexer_grpc_spot_api.py | 75 +++ .../test_indexer_grpc_spot_stream.py | 584 ++++++++++++++++++ .../test_async_client_deprecation_warnings.py | 112 +++- 15 files changed, 1383 insertions(+), 111 deletions(-) delete mode 100644 examples/exchange_client/spot_exchange_rpc/13_StreamOrderbooks.py create mode 100644 pyinjective/client/indexer/grpc_stream/indexer_grpc_spot_stream.py create mode 100644 tests/client/indexer/stream_grpc/test_indexer_grpc_spot_stream.py diff --git a/examples/exchange_client/spot_exchange_rpc/10_StreamTrades.py b/examples/exchange_client/spot_exchange_rpc/10_StreamTrades.py index 8bf4a82a..a1d63511 100644 --- a/examples/exchange_client/spot_exchange_rpc/10_StreamTrades.py +++ b/examples/exchange_client/spot_exchange_rpc/10_StreamTrades.py @@ -1,9 +1,24 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def trade_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to spot trades updates ({exception})") + + +def stream_closed_processor(): + print("The spot trades updates stream has been closed") + + async def main() -> None: network = Network.testnet() client = AsyncClient(network) @@ -15,15 +30,22 @@ async def main() -> None: direction = "sell" subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001" execution_types = ["limitMatchRestingOrder"] - trades = await client.stream_spot_trades( - market_ids=market_ids, - execution_side=execution_side, - direction=direction, - subaccount_id=subaccount_id, - execution_types=execution_types, + + task = asyncio.get_event_loop().create_task( + client.listen_spot_trades_updates( + callback=trade_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + market_ids=market_ids, + subaccount_ids=[subaccount_id], + execution_side=execution_side, + direction=direction, + execution_types=execution_types, + ) ) - async for trade in trades: - print(trade) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/spot_exchange_rpc/13_StreamOrderbooks.py b/examples/exchange_client/spot_exchange_rpc/13_StreamOrderbooks.py deleted file mode 100644 index 82fe35e7..00000000 --- a/examples/exchange_client/spot_exchange_rpc/13_StreamOrderbooks.py +++ /dev/null @@ -1,20 +0,0 @@ -import asyncio - -from pyinjective.async_client import AsyncClient -from pyinjective.core.network import Network - - -async def main() -> None: - network = Network.testnet() - client = AsyncClient(network) - market_ids = [ - "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", - "0x7a57e705bb4e09c88aecfc295569481dbf2fe1d5efe364651fbe72385938e9b0", - ] - orderbook = await client.stream_spot_orderbook_snapshot(market_ids=market_ids) - async for orders in orderbook: - print(orders) - - -if __name__ == "__main__": - asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/exchange_client/spot_exchange_rpc/3_StreamMarkets.py b/examples/exchange_client/spot_exchange_rpc/3_StreamMarkets.py index 16633271..ac56d2d7 100644 --- a/examples/exchange_client/spot_exchange_rpc/3_StreamMarkets.py +++ b/examples/exchange_client/spot_exchange_rpc/3_StreamMarkets.py @@ -1,16 +1,39 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def market_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to spot markets updates ({exception})") + + +def stream_closed_processor(): + print("The spot markets updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet - network = Network.testnet() + network = Network.mainnet() client = AsyncClient(network) - markets = await client.stream_spot_markets() - async for market in markets: - print(market) + + task = asyncio.get_event_loop().create_task( + client.listen_spot_markets_updates( + callback=market_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/spot_exchange_rpc/7_StreamOrderbookSnapshot.py b/examples/exchange_client/spot_exchange_rpc/7_StreamOrderbookSnapshot.py index 5c4ed593..7eacf4df 100644 --- a/examples/exchange_client/spot_exchange_rpc/7_StreamOrderbookSnapshot.py +++ b/examples/exchange_client/spot_exchange_rpc/7_StreamOrderbookSnapshot.py @@ -1,17 +1,43 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def orderbook_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to spot orderbook snapshots ({exception})") + + +def stream_closed_processor(): + print("The spot orderbook snapshots stream has been closed") + + async def main() -> None: - # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_ids = ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"] - orderbooks = await client.stream_spot_orderbook_snapshot(market_ids=market_ids) - async for orderbook in orderbooks: - print(orderbook) + market_ids = [ + "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + "0x7a57e705bb4e09c88aecfc295569481dbf2fe1d5efe364651fbe72385938e9b0", + ] + + task = asyncio.get_event_loop().create_task( + client.listen_spot_orderbook_snapshots( + market_ids=market_ids, + callback=orderbook_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/spot_exchange_rpc/8_StreamOrderbookUpdate.py b/examples/exchange_client/spot_exchange_rpc/8_StreamOrderbookUpdate.py index 13a789fd..6a7fb247 100644 --- a/examples/exchange_client/spot_exchange_rpc/8_StreamOrderbookUpdate.py +++ b/examples/exchange_client/spot_exchange_rpc/8_StreamOrderbookUpdate.py @@ -1,10 +1,21 @@ import asyncio from decimal import Decimal +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to spot orderbook updates ({exception})") + + +def stream_closed_processor(): + print("The spot orderbook updates stream has been closed") + + class PriceLevel: def __init__(self, price: Decimal, quantity: Decimal, timestamp: int): self.price = price @@ -24,24 +35,24 @@ def __init__(self, market_id: str): async def load_orderbook_snapshot(async_client: AsyncClient, orderbook: Orderbook): # load the snapshot - res = await async_client.get_spot_orderbooksV2(market_ids=[orderbook.market_id]) - for snapshot in res.orderbooks: - if snapshot.market_id != orderbook.market_id: + res = await async_client.fetch_spot_orderbooks_v2(market_ids=[orderbook.market_id]) + for snapshot in res["orderbooks"]: + if snapshot["marketId"] != orderbook.market_id: raise Exception("unexpected snapshot") - orderbook.sequence = int(snapshot.orderbook.sequence) + orderbook.sequence = int(snapshot["orderbook"]["sequence"]) - for buy in snapshot.orderbook.buys: - orderbook.levels["buys"][buy.price] = PriceLevel( - price=Decimal(buy.price), - quantity=Decimal(buy.quantity), - timestamp=buy.timestamp, + for buy in snapshot["orderbook"]["buys"]: + orderbook.levels["buys"][buy["price"]] = PriceLevel( + price=Decimal(buy["price"]), + quantity=Decimal(buy["quantity"]), + timestamp=int(buy["timestamp"]), ) - for sell in snapshot.orderbook.sells: - orderbook.levels["sells"][sell.price] = PriceLevel( - price=Decimal(sell.price), - quantity=Decimal(sell.quantity), - timestamp=sell.timestamp, + for sell in snapshot["orderbook"]["sells"]: + orderbook.levels["sells"][sell["price"]] = PriceLevel( + price=Decimal(sell["price"]), + quantity=Decimal(sell["quantity"]), + timestamp=int(sell["timestamp"]), ) break @@ -53,74 +64,91 @@ async def main() -> None: market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" orderbook = Orderbook(market_id=market_id) + updates_queue = asyncio.Queue() + tasks = [] + + async def queue_event(event: Dict[str, Any]): + await updates_queue.put(event) # start getting price levels updates - stream = await async_client.stream_spot_orderbook_update(market_ids=[market_id]) - first_update = None - async for update in stream: - first_update = update.orderbook_level_updates - break + task = asyncio.get_event_loop().create_task( + async_client.listen_spot_orderbook_updates( + market_ids=[market_id], + callback=queue_event, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + tasks.append(task) # load the snapshot once we are already receiving updates, so we don't miss any await load_orderbook_snapshot(async_client=async_client, orderbook=orderbook) - # start consuming updates again to process them - apply_orderbook_update(orderbook, first_update) - async for update in stream: - apply_orderbook_update(orderbook, update.orderbook_level_updates) + task = asyncio.get_event_loop().create_task( + apply_orderbook_update(orderbook=orderbook, updates_queue=updates_queue) + ) + tasks.append(task) + await asyncio.sleep(delay=60) + for task in tasks: + task.cancel() -def apply_orderbook_update(orderbook: Orderbook, updates): - # discard updates older than the snapshot - if updates.sequence <= orderbook.sequence: - return - print(" * * * * * * * * * * * * * * * * * * *") +async def apply_orderbook_update(orderbook: Orderbook, updates_queue: asyncio.Queue): + while True: + updates = await updates_queue.get() + update = updates["orderbookLevelUpdates"] - # ensure we have not missed any update - if updates.sequence > (orderbook.sequence + 1): - raise Exception( - "missing orderbook update events from stream, must restart: {} vs {}".format( - updates.sequence, (orderbook.sequence + 1) - ) - ) + # discard updates older than the snapshot + if int(update["sequence"]) <= orderbook.sequence: + return - print("updating orderbook with updates at sequence {}".format(updates.sequence)) + print(" * * * * * * * * * * * * * * * * * * *") - # update orderbook - orderbook.sequence = updates.sequence - for direction, levels in {"buys": updates.buys, "sells": updates.sells}.items(): - for level in levels: - if level.is_active: - # upsert level - orderbook.levels[direction][level.price] = PriceLevel( - price=Decimal(level.price), quantity=Decimal(level.quantity), timestamp=level.timestamp + # ensure we have not missed any update + if int(update["sequence"]) > (orderbook.sequence + 1): + raise Exception( + "missing orderbook update events from stream, must restart: {} vs {}".format( + update["sequence"], (orderbook.sequence + 1) ) - else: - if level.price in orderbook.levels[direction]: - del orderbook.levels[direction][level.price] - - # sort the level numerically - buys = sorted(orderbook.levels["buys"].values(), key=lambda x: x.price, reverse=True) - sells = sorted(orderbook.levels["sells"].values(), key=lambda x: x.price, reverse=True) - - # lowest sell price should be higher than the highest buy price - if len(buys) > 0 and len(sells) > 0: - highest_buy = buys[0].price - lowest_sell = sells[-1].price - print("Max buy: {} - Min sell: {}".format(highest_buy, lowest_sell)) - if highest_buy >= lowest_sell: - raise Exception("crossed orderbook, must restart") - - # for the example, print the list of buys and sells orders. - print("sells") - for k in sells: - print(k) - print("=========") - print("buys") - for k in buys: - print(k) - print("====================================") + ) + + print("updating orderbook with updates at sequence {}".format(update["sequence"])) + + # update orderbook + orderbook.sequence = int(update["sequence"]) + for direction, levels in {"buys": update["buys"], "sells": update["sells"]}.items(): + for level in levels: + if level["isActive"]: + # upsert level + orderbook.levels[direction][level["price"]] = PriceLevel( + price=Decimal(level["price"]), quantity=Decimal(level["quantity"]), timestamp=level["timestamp"] + ) + else: + if level["price"] in orderbook.levels[direction]: + del orderbook.levels[direction][level["price"]] + + # sort the level numerically + buys = sorted(orderbook.levels["buys"].values(), key=lambda x: x.price, reverse=True) + sells = sorted(orderbook.levels["sells"].values(), key=lambda x: x.price, reverse=True) + + # lowest sell price should be higher than the highest buy price + if len(buys) > 0 and len(sells) > 0: + highest_buy = buys[0].price + lowest_sell = sells[-1].price + print("Max buy: {} - Min sell: {}".format(highest_buy, lowest_sell)) + if highest_buy >= lowest_sell: + raise Exception("crossed orderbook, must restart") + + # for the example, print the list of buys and sells orders. + print("sells") + for k in sells: + print(k) + print("=========") + print("buys") + for k in buys: + print(k) + print("====================================") if __name__ == "__main__": diff --git a/examples/exchange_client/spot_exchange_rpc/9_StreamHistoricalOrders.py b/examples/exchange_client/spot_exchange_rpc/9_StreamHistoricalOrders.py index 95dcdb43..91ed7810 100644 --- a/examples/exchange_client/spot_exchange_rpc/9_StreamHistoricalOrders.py +++ b/examples/exchange_client/spot_exchange_rpc/9_StreamHistoricalOrders.py @@ -1,17 +1,42 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def order_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to spot orders history updates ({exception})") + + +def stream_closed_processor(): + print("The spot orders history updates stream has been closed") + + async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" - order_side = "buy" - orders = await client.stream_historical_spot_orders(market_id=market_id, order_side=order_side) - async for order in orders: - print(order) + order_direction = "buy" + + task = asyncio.get_event_loop().create_task( + client.listen_spot_orders_history_updates( + callback=order_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + market_id=market_id, + direction=order_direction, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index a1bc3fc0..8e6b3c5c 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -22,6 +22,7 @@ from pyinjective.client.indexer.grpc_stream.indexer_grpc_auction_stream import IndexerGrpcAuctionStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_oracle_stream import IndexerGrpcOracleStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_spot_stream import IndexerGrpcSpotStream from pyinjective.client.model.pagination import PaginationOption from pyinjective.composer import Composer from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket @@ -235,6 +236,12 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_spot_stream_api = IndexerGrpcSpotStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) async def all_tokens(self) -> Dict[str, Token]: if self._tokens is None: @@ -962,12 +969,31 @@ async def fetch_spot_markets( ) async def stream_spot_markets(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_markets_updates` instead + """ + warn("This method is deprecated. Use listen_spot_markets_updates instead", DeprecationWarning, stacklevel=2) + req = spot_exchange_rpc_pb.StreamMarketsRequest(market_ids=kwargs.get("market_ids")) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubSpotExchange.StreamMarkets(request=req, metadata=metadata) + async def listen_spot_markets_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + ): + await self.exchange_spot_stream_api.stream_markets( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + ) + async def get_spot_orderbookV2(self, market_id: str): """ This method is deprecated and will be removed soon. Please use `fetch_spot_orderbook_v2` instead @@ -1136,20 +1162,60 @@ async def fetch_spot_trades( ) async def stream_spot_orderbook_snapshot(self, market_ids: List[str]): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_orderbook_snapshots` instead + """ + warn("This method is deprecated. Use listen_spot_orderbook_snapshots instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.StreamOrderbookV2Request(market_ids=market_ids) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubSpotExchange.StreamOrderbookV2(request=req, metadata=metadata) + async def listen_spot_orderbook_snapshots( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_spot_stream_api.stream_orderbook_v2( + market_ids=market_ids, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def stream_spot_orderbook_update(self, market_ids: List[str]): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_orderbook_updates` instead + """ + warn("This method is deprecated. Use listen_spot_orderbook_updates instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.StreamOrderbookUpdateRequest(market_ids=market_ids) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubSpotExchange.StreamOrderbookUpdate(request=req, metadata=metadata) + async def listen_spot_orderbook_updates( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_spot_stream_api.stream_orderbook_update( + market_ids=market_ids, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def stream_spot_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_orders_updates` instead + """ + warn("This method is deprecated. Use listen_spot_orders_updates instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.StreamOrdersRequest( market_id=market_id, order_side=kwargs.get("order_side"), @@ -1169,7 +1235,43 @@ async def stream_spot_orders(self, market_id: str, **kwargs): ) return self.stubSpotExchange.StreamOrders(request=req, metadata=metadata) + async def listen_spot_orders_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[PaginationOption] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + await self.exchange_spot_stream_api.stream_orders( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + pagination=pagination, + ) + async def stream_historical_spot_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_orders_history_updates` instead + """ + warn( + "This method is deprecated. Use listen_spot_orders_history_updates instead", + DeprecationWarning, + stacklevel=2, + ) req = spot_exchange_rpc_pb.StreamOrdersHistoryRequest( market_id=market_id, direction=kwargs.get("direction"), @@ -1183,6 +1285,30 @@ async def stream_historical_spot_orders(self, market_id: str, **kwargs): ) return self.stubSpotExchange.StreamOrdersHistory(request=req, metadata=metadata) + async def listen_spot_orders_history_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + market_id: Optional[str] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + ): + await self.exchange_spot_stream_api.stream_orders_history( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + subaccount_id=subaccount_id, + market_id=market_id, + order_types=order_types, + direction=direction, + state=state, + execution_types=execution_types, + ) + async def stream_historical_derivative_orders(self, market_id: str, **kwargs): req = derivative_exchange_rpc_pb.StreamOrdersHistoryRequest( market_id=market_id, @@ -1198,6 +1324,10 @@ async def stream_historical_derivative_orders(self, market_id: str, **kwargs): return self.stubDerivativeExchange.StreamOrdersHistory(request=req, metadata=metadata) async def stream_spot_trades(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_trades_updates` instead + """ + warn("This method is deprecated. Use listen_spot_trades_updates instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.StreamTradesRequest( market_id=kwargs.get("market_id"), execution_side=kwargs.get("execution_side"), @@ -1219,6 +1349,36 @@ async def stream_spot_trades(self, **kwargs): ) return self.stubSpotExchange.StreamTrades(request=req, metadata=metadata) + async def listen_spot_trades_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + await self.exchange_spot_stream_api.stream_trades( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + pagination=pagination, + ) + async def get_spot_subaccount_orders(self, subaccount_id: str, **kwargs): """ This method is deprecated and will be removed soon. Please use `fetch_spot_subaccount_orders_list` instead diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py index 7f3f737a..5b9a0b40 100644 --- a/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py +++ b/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py @@ -184,5 +184,25 @@ async def fetch_orders_history( return response + async def fetch_atomic_swap_history( + self, + address: str, + contract_address: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.AtomicSwapHistoryRequest( + address=address, + contract_address=contract_address, + skip=pagination.skip, + limit=pagination.limit, + from_number=pagination.from_number, + to_number=pagination.to_number, + ) + + response = await self._execute_call(call=self._stub.AtomicSwapHistory, request=request) + + return response + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_spot_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_spot_stream.py new file mode 100644 index 00000000..f8772460 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_spot_stream.py @@ -0,0 +1,174 @@ +from typing import Callable, List, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.exchange import ( + injective_spot_exchange_rpc_pb2 as exchange_spot_pb, + injective_spot_exchange_rpc_pb2_grpc as exchange_spot_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcSpotStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_spot_grpc.InjectiveSpotExchangeRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_markets( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + ): + request = exchange_spot_pb.StreamMarketsRequest( + market_ids=market_ids, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamMarkets, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orderbook_v2( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_spot_pb.StreamOrderbookV2Request(market_ids=market_ids) + + await self._assistant.listen_stream( + call=self._stub.StreamOrderbookV2, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orderbook_update( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_spot_pb.StreamOrderbookUpdateRequest(market_ids=market_ids) + + await self._assistant.listen_stream( + call=self._stub.StreamOrderbookUpdate, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orders( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[PaginationOption] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + pagination = pagination or PaginationOption() + request = exchange_spot_pb.StreamOrdersRequest( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamOrders, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_trades( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + pagination = pagination or PaginationOption() + request = exchange_spot_pb.StreamTradesRequest( + execution_side=execution_side, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamTrades, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orders_history( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + market_id: Optional[str] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + ): + request = exchange_spot_pb.StreamOrdersHistoryRequest( + subaccount_id=subaccount_id, + market_id=market_id, + order_types=order_types, + direction=direction, + state=state, + execution_types=execution_types, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamOrdersHistory, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/model/pagination.py b/pyinjective/client/model/pagination.py index bab737db..f3607c17 100644 --- a/pyinjective/client/model/pagination.py +++ b/pyinjective/client/model/pagination.py @@ -13,6 +13,8 @@ def __init__( end_time: Optional[int] = None, reverse: Optional[bool] = None, count_total: Optional[bool] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, ): super().__init__() self.key = key @@ -22,6 +24,8 @@ def __init__( self.end_time = end_time self.reverse = reverse self.count_total = count_total + self.from_number = from_number + self.to_number = to_number def create_pagination_request(self) -> pagination_pb.PageRequest: page_request = pagination_pb.PageRequest() diff --git a/pyinjective/utils/grpc_api_stream_assistant.py b/pyinjective/utils/grpc_api_stream_assistant.py index 50092def..00a8c331 100644 --- a/pyinjective/utils/grpc_api_stream_assistant.py +++ b/pyinjective/utils/grpc_api_stream_assistant.py @@ -37,6 +37,8 @@ async def listen_stream( await on_status_callback(ex) else: on_status_callback(ex) + except Exception as all_ex: + print(all_ex) if on_end_callback is not None: if asyncio.iscoroutinefunction(on_end_callback): diff --git a/tests/client/indexer/configurable_spot_query_servicer.py b/tests/client/indexer/configurable_spot_query_servicer.py index cdfbc336..7104d4da 100644 --- a/tests/client/indexer/configurable_spot_query_servicer.py +++ b/tests/client/indexer/configurable_spot_query_servicer.py @@ -18,6 +18,14 @@ def __init__(self): self.subaccount_orders_list_responses = deque() self.subaccount_trades_list_responses = deque() self.orders_history_responses = deque() + self.atomic_swap_history_responses = deque() + + self.stream_markets_responses = deque() + self.stream_orderbook_v2_responses = deque() + self.stream_orderbook_update_responses = deque() + self.stream_orders_responses = deque() + self.stream_trades_responses = deque() + self.stream_orders_history_responses = deque() async def Markets(self, request: exchange_spot_pb.MarketsRequest, context=None, metadata=None): return self.markets_responses.pop() @@ -49,3 +57,34 @@ async def SubaccountTradesList( async def OrdersHistory(self, request: exchange_spot_pb.OrdersHistoryRequest, context=None, metadata=None): return self.orders_history_responses.pop() + + async def AtomicSwapHistory(self, request: exchange_spot_pb.AtomicSwapHistoryRequest, context=None, metadata=None): + return self.atomic_swap_history_responses.pop() + + async def StreamMarkets(self, request: exchange_spot_pb.StreamMarketsRequest, context=None, metadata=None): + for event in self.stream_markets_responses: + yield event + + async def StreamOrderbookV2(self, request: exchange_spot_pb.StreamOrderbookV2Request, context=None, metadata=None): + for event in self.stream_orderbook_v2_responses: + yield event + + async def StreamOrderbookUpdate( + self, request: exchange_spot_pb.StreamOrderbookUpdateRequest, context=None, metadata=None + ): + for event in self.stream_orderbook_update_responses: + yield event + + async def StreamOrders(self, request: exchange_spot_pb.StreamOrdersRequest, context=None, metadata=None): + for event in self.stream_orders_responses: + yield event + + async def StreamTrades(self, request: exchange_spot_pb.StreamTradesRequest, context=None, metadata=None): + for event in self.stream_trades_responses: + yield event + + async def StreamOrdersHistory( + self, request: exchange_spot_pb.StreamOrdersHistoryRequest, context=None, metadata=None + ): + for event in self.stream_orders_history_responses: + yield event diff --git a/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py b/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py index 4dad0933..b84d30d9 100644 --- a/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py +++ b/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py @@ -727,5 +727,80 @@ async def test_fetch_orders_history( assert result_orders == expected_orders + @pytest.mark.asyncio + async def test_fetch_atomic_swap_history( + self, + spot_servicer, + ): + source_coin = exchange_spot_pb.Coin(denom="inj", amount="988987297011197594664") + dest_coin = exchange_spot_pb.Coin(denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", amount="54497408") + fee = exchange_spot_pb.Coin(denom="inj", amount="100000") + + atomic_swap = exchange_spot_pb.AtomicSwap( + sender="sender", + route="route", + source_coin=source_coin, + dest_coin=dest_coin, + fees=[fee], + contract_address="contract address", + index_by_sender=1, + index_by_sender_contract=2, + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + executed_at=1699644939364, + refund_amount="0", + ) + paging = exchange_spot_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + spot_servicer.atomic_swap_history_responses.append( + exchange_spot_pb.AtomicSwapHistoryResponse( + data=[atomic_swap], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_history = await api.fetch_atomic_swap_history( + address=atomic_swap.sender, + contract_address=atomic_swap.contract_address, + pagination=PaginationOption( + skip=0, + limit=100, + from_number=1, + to_number=100, + ), + ) + expected_history = { + "data": [ + { + "contractAddress": atomic_swap.contract_address, + "destCoin": {"amount": dest_coin.amount, "denom": dest_coin.denom}, + "executedAt": str(atomic_swap.executed_at), + "fees": [{"amount": fee.amount, "denom": fee.denom}], + "indexBySender": atomic_swap.index_by_sender, + "indexBySenderContract": atomic_swap.index_by_sender_contract, + "refundAmount": atomic_swap.refund_amount, + "route": atomic_swap.route, + "sender": atomic_swap.sender, + "sourceCoin": {"amount": source_coin.amount, "denom": source_coin.denom}, + "txHash": atomic_swap.tx_hash, + } + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_history == expected_history + async def _dummy_metadata_provider(self): return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_spot_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_spot_stream.py new file mode 100644 index 00000000..d980e143 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_spot_stream.py @@ -0,0 +1,584 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_spot_stream import IndexerGrpcSpotStream +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_spot_exchange_rpc_pb2 as exchange_spot_pb +from tests.client.indexer.configurable_spot_query_servicer import ConfigurableSpotQueryServicer + + +@pytest.fixture +def spot_servicer(): + return ConfigurableSpotQueryServicer() + + +class TestIndexerGrpcSpotStream: + @pytest.mark.asyncio + async def test_stream_markets( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + base_token_meta = exchange_spot_pb.TokenMeta( + name="Injective Protocol", + address="0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", + symbol="INJ", + logo="https://static.alchemyapi.io/images/assets/7226.png", + decimals=18, + updated_at=1683119359318, + ) + quote_token_meta = exchange_spot_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + + market = exchange_spot_pb.SpotMarketInfo( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + market_status="active", + ticker="INJ/USDT", + base_denom="inj", + base_token_meta=base_token_meta, + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + min_price_tick_size="0.000000000000001", + min_quantity_tick_size="1000000000000000", + ) + + spot_servicer.stream_markets_responses.append( + exchange_spot_pb.StreamMarketsResponse( + market=market, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + market_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: market_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_markets( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[market.market_id], + ) + ) + expected_update = { + "market": { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "baseDenom": market.base_denom, + "baseTokenMeta": { + "name": market.base_token_meta.name, + "address": market.base_token_meta.address, + "symbol": market.base_token_meta.symbol, + "logo": market.base_token_meta.logo, + "decimals": market.base_token_meta.decimals, + "updatedAt": str(market.base_token_meta.updated_at), + }, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "takerFeeRate": market.taker_fee_rate, + "makerFeeRate": market.maker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(market_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orderbook_v2( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + + buy = exchange_spot_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_spot_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_spot_pb.SpotLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + spot_servicer.stream_orderbook_v2_responses.append( + exchange_spot_pb.StreamOrderbookV2Response( + orderbook=orderbook, + operation_type=operation_type, + timestamp=timestamp, + market_id=market_id, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + orderbook_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orderbook_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orderbook_v2( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[market_id], + ) + ) + expected_update = { + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + }, + "operationType": operation_type, + "timestamp": str(timestamp), + "marketId": market_id, + } + + first_update = await asyncio.wait_for(orderbook_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orderbook_update( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + buy = exchange_spot_pb.PriceLevelUpdate( + price="0.000000000014198", + quantity="142000000000000000000", + is_active=True, + timestamp=1698982052141, + ) + sell = exchange_spot_pb.PriceLevelUpdate( + price="0.00000000095699", + quantity="189000000000000000", + is_active=True, + timestamp=1698920369246, + ) + + level_updates = exchange_spot_pb.OrderbookLevelUpdates( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + sequence=5506752, + buys=[buy], + sells=[sell], + updated_at=1698982083606, + ) + + spot_servicer.stream_orderbook_update_responses.append( + exchange_spot_pb.StreamOrderbookUpdateResponse( + orderbook_level_updates=level_updates, + operation_type=operation_type, + timestamp=timestamp, + market_id=level_updates.market_id, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + orderbook_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orderbook_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orderbook_update( + market_ids=[level_updates.market_id], + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "orderbookLevelUpdates": { + "marketId": level_updates.market_id, + "sequence": str(level_updates.sequence), + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "isActive": buy.is_active, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "isActive": sell.is_active, + "timestamp": str(sell.timestamp), + } + ], + "updatedAt": str(level_updates.updated_at), + }, + "operationType": operation_type, + "timestamp": str(timestamp), + "marketId": level_updates.market_id, + } + + first_update = await asyncio.wait_for(orderbook_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orders( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + order = exchange_spot_pb.SpotLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + spot_servicer.stream_orders_responses.append( + exchange_spot_pb.StreamOrdersResponse( + order=order, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + orders_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orders_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orders( + market_ids=[order.market_id], + order_side=order.order_side, + subaccount_id=order.subaccount_id, + include_inactive=True, + subaccount_total_orders=True, + trade_id="7959737_3_0", + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "order": { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "txHash": order.tx_hash, + "cid": order.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(orders_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_trades( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + price = exchange_spot_pb.PriceLevel( + price="0.000000000006024", + quantity="10000000000000000", + timestamp=1677563766350, + ) + + trade = exchange_spot_pb.SpotTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + trade_execution_type="limitMatchNewOrder", + trade_direction="buy", + price=price, + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + spot_servicer.stream_trades_responses.append( + exchange_spot_pb.StreamTradesResponse( + trade=trade, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + trade_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: trade_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_trades( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[trade.market_id], + subaccount_ids=[trade.subaccount_id], + execution_side=trade.execution_side, + direction=trade.trade_direction, + execution_types=[trade.trade_execution_type], + trade_id="7959737_3_0", + account_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + cid=trade.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + ) + expected_update = { + "trade": { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "tradeDirection": trade.trade_direction, + "price": { + "price": price.price, + "quantity": price.quantity, + "timestamp": str(price.timestamp), + }, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(trade_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orders_history( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + order = exchange_spot_pb.SpotOrderHistory( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + is_active=True, + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + execution_type="limit", + order_type="buy_po", + price="0.000000000017541", + trigger_price="0", + quantity="50955000000000000000", + filled_quantity="1000000000000000", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + direction="buy", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + spot_servicer.stream_orders_history_responses.append( + exchange_spot_pb.StreamOrdersHistoryResponse( + order=order, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + orders_history_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orders_history_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orders_history( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + subaccount_id=order.subaccount_id, + market_id=order.market_id, + order_types=[order.order_type], + direction=order.direction, + state=order.state, + execution_types=[order.execution_type], + ) + ) + expected_update = { + "order": { + "orderHash": order.order_hash, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "executionType": order.execution_type, + "orderType": order.order_type, + "price": order.price, + "triggerPrice": order.trigger_price, + "quantity": order.quantity, + "filledQuantity": order.filled_quantity, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "direction": order.direction, + "txHash": order.tx_hash, + "isActive": order.is_active, + "cid": order.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(orders_history_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index fe10273a..4bbf2cab 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -827,7 +827,7 @@ async def test_get_historical_spot_orders_deprecation_warning( network=Network.local(), ) client.stubSpotExchange = spot_servicer - spot_servicer.subaccount_trades_list_responses.append(exchange_spot_pb.SubaccountTradesListResponse()) + spot_servicer.orders_history_responses.append(exchange_spot_pb.SubaccountTradesListResponse()) with catch_warnings(record=True) as all_warnings: await client.get_historical_spot_orders() @@ -835,3 +835,113 @@ async def test_get_historical_spot_orders_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_orders_history instead" + + @pytest.mark.asyncio + async def test_stream_spot_markets_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_markets_responses.append(exchange_spot_pb.StreamMarketsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_spot_markets() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_markets_updates instead" + + @pytest.mark.asyncio + async def test_stream_spot_orderbook_snapshot_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_orderbook_v2_responses.append(exchange_spot_pb.StreamOrderbookV2Response()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_spot_orderbook_snapshot(market_ids=[]) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_orderbook_snapshots instead" + + @pytest.mark.asyncio + async def test_stream_spot_orderbook_update_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_orderbook_v2_responses.append(exchange_spot_pb.StreamOrderbookUpdateRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_spot_orderbook_update(market_ids=[]) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_orderbook_updates instead" + + @pytest.mark.asyncio + async def test_stream_spot_orders_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_orders_responses.append(exchange_spot_pb.StreamOrdersRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_spot_orders(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_orders_updates instead" + + @pytest.mark.asyncio + async def test_stream_spot_trades_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_orders_responses.append(exchange_spot_pb.StreamTradesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_spot_trades() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_trades_updates instead" + + @pytest.mark.asyncio + async def test_stream_historical_spot_orders_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_orders_history_responses.append(exchange_spot_pb.StreamOrdersHistoryRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_historical_spot_orders(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_orders_history_updates instead" + ) From a549e62a0ebf8eeb65301a58e39f53fd085ddfcf Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 17 Nov 2023 16:58:08 -0300 Subject: [PATCH 19/27] (feat) Added exchange derivative module gRPC requests to the low level API components. Created the unit tests for the new functionality. Added new functions in AsyncClient using the API components and deprecated the old functions --- .../derivative_exchange_rpc/11_Trades.py | 12 +- .../13_SubaccountOrdersList.py | 6 +- .../14_SubaccountTradesList.py | 7 +- .../15_FundingPayments.py | 10 +- .../17_FundingRates.py | 4 +- .../derivative_exchange_rpc/18_Orderbooks.py | 20 - .../19_Binary_Options_Markets.py | 2 +- .../derivative_exchange_rpc/1_Market.py | 2 +- .../20_Binary_Options_Market.py | 2 +- .../21_Historical_Orders.py | 11 +- .../22_OrderbooksV2.py | 2 +- .../23_LiquidablePositions.py | 17 +- .../derivative_exchange_rpc/2_Markets.py | 4 +- .../derivative_exchange_rpc/4_Orderbook.py | 2 +- .../6_StreamOrderbookUpdate.py | 28 +- .../derivative_exchange_rpc/7_Positions.py | 9 +- pyinjective/async_client.py | 292 +++- .../grpc/indexer_grpc_derivative_api.py | 291 ++++ pyinjective/core/market.py | 1 + .../configurable_derivative_query_servicer.py | 83 ++ .../grpc/test_indexer_grpc_derivative_api.py | 1236 +++++++++++++++++ tests/rpc_fixtures/configurable_servicers.py | 29 - tests/test_async_client.py | 20 +- .../test_async_client_deprecation_warnings.py | 290 ++++ 24 files changed, 2251 insertions(+), 129 deletions(-) delete mode 100644 examples/exchange_client/derivative_exchange_rpc/18_Orderbooks.py create mode 100644 pyinjective/client/indexer/grpc/indexer_grpc_derivative_api.py create mode 100644 tests/client/indexer/configurable_derivative_query_servicer.py create mode 100644 tests/client/indexer/grpc/test_indexer_grpc_derivative_api.py delete mode 100644 tests/rpc_fixtures/configurable_servicers.py diff --git a/examples/exchange_client/derivative_exchange_rpc/11_Trades.py b/examples/exchange_client/derivative_exchange_rpc/11_Trades.py index 5ae822a9..3befd828 100644 --- a/examples/exchange_client/derivative_exchange_rpc/11_Trades.py +++ b/examples/exchange_client/derivative_exchange_rpc/11_Trades.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -8,9 +9,14 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - subaccount_id = "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000" - trades = await client.get_derivative_trades(market_id=market_id, subaccount_id=subaccount_id) + market_ids = ["0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"] + subaccount_ids = ["0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000"] + skip = 0 + limit = 4 + pagination = PaginationOption(skip=skip, limit=limit) + trades = await client.fetch_derivative_trades( + market_ids=market_ids, subaccount_ids=subaccount_ids, pagination=pagination + ) print(trades) diff --git a/examples/exchange_client/derivative_exchange_rpc/13_SubaccountOrdersList.py b/examples/exchange_client/derivative_exchange_rpc/13_SubaccountOrdersList.py index a7f06db7..d219a0a2 100644 --- a/examples/exchange_client/derivative_exchange_rpc/13_SubaccountOrdersList.py +++ b/examples/exchange_client/derivative_exchange_rpc/13_SubaccountOrdersList.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -12,8 +13,9 @@ async def main() -> None: market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" skip = 1 limit = 2 - orders = await client.get_derivative_subaccount_orders( - subaccount_id=subaccount_id, market_id=market_id, skip=skip, limit=limit + pagination = PaginationOption(skip=skip, limit=limit) + orders = await client.fetch_subaccount_orders_list( + subaccount_id=subaccount_id, market_id=market_id, pagination=pagination ) print(orders) diff --git a/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py b/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py index 4cd55f06..c1e703e3 100644 --- a/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py +++ b/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -14,13 +15,13 @@ async def main() -> None: direction = "sell" skip = 10 limit = 2 - trades = await client.get_derivative_subaccount_trades( + pagination = PaginationOption(skip=skip, limit=limit) + trades = await client.fetch_subaccount_trades_list( subaccount_id=subaccount_id, market_id=market_id, execution_type=execution_type, direction=direction, - skip=skip, - limit=limit, + pagination=pagination, ) print(trades) diff --git a/examples/exchange_client/derivative_exchange_rpc/15_FundingPayments.py b/examples/exchange_client/derivative_exchange_rpc/15_FundingPayments.py index 8f352c9e..5321d723 100644 --- a/examples/exchange_client/derivative_exchange_rpc/15_FundingPayments.py +++ b/examples/exchange_client/derivative_exchange_rpc/15_FundingPayments.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -8,15 +9,16 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" + market_ids = ["0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"] subaccount_id = "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000" skip = 0 limit = 3 end_time = 1676426400125 - funding = await client.get_funding_payments( - market_id=market_id, subaccount_id=subaccount_id, skip=skip, limit=limit, end_time=end_time + pagination = PaginationOption(skip=skip, limit=limit, end_time=end_time) + funding_payments = await client.fetch_funding_payments( + market_ids=market_ids, subaccount_id=subaccount_id, pagination=pagination ) - print(funding) + print(funding_payments) if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/17_FundingRates.py b/examples/exchange_client/derivative_exchange_rpc/17_FundingRates.py index 2a8cbcd1..f7bc3b7f 100644 --- a/examples/exchange_client/derivative_exchange_rpc/17_FundingRates.py +++ b/examples/exchange_client/derivative_exchange_rpc/17_FundingRates.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -12,7 +13,8 @@ async def main() -> None: skip = 0 limit = 3 end_time = 1675717201465 - funding_rates = await client.get_funding_rates(market_id=market_id, skip=skip, limit=limit, end_time=end_time) + pagination = PaginationOption(skip=skip, limit=limit, end_time=end_time) + funding_rates = await client.fetch_funding_rates(market_id=market_id, pagination=pagination) print(funding_rates) diff --git a/examples/exchange_client/derivative_exchange_rpc/18_Orderbooks.py b/examples/exchange_client/derivative_exchange_rpc/18_Orderbooks.py deleted file mode 100644 index 49b00e02..00000000 --- a/examples/exchange_client/derivative_exchange_rpc/18_Orderbooks.py +++ /dev/null @@ -1,20 +0,0 @@ -import asyncio - -from pyinjective.async_client import AsyncClient -from pyinjective.core.network import Network - - -async def main() -> None: - # select network: local, testnet, mainnet - network = Network.testnet() - client = AsyncClient(network) - market_ids = [ - "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", - "0xd5e4b12b19ecf176e4e14b42944731c27677819d2ed93be4104ad7025529c7ff", - ] - markets = await client.get_derivative_orderbooks(market_ids=market_ids) - print(markets) - - -if __name__ == "__main__": - asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/exchange_client/derivative_exchange_rpc/19_Binary_Options_Markets.py b/examples/exchange_client/derivative_exchange_rpc/19_Binary_Options_Markets.py index 9ed36e38..97b90bca 100644 --- a/examples/exchange_client/derivative_exchange_rpc/19_Binary_Options_Markets.py +++ b/examples/exchange_client/derivative_exchange_rpc/19_Binary_Options_Markets.py @@ -9,7 +9,7 @@ async def main() -> None: client = AsyncClient(network) market_status = "active" quote_denom = "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7" - market = await client.get_binary_options_markets(market_status=market_status, quote_denom=quote_denom) + market = await client.fetch_binary_options_markets(market_status=market_status, quote_denom=quote_denom) print(market) diff --git a/examples/exchange_client/derivative_exchange_rpc/1_Market.py b/examples/exchange_client/derivative_exchange_rpc/1_Market.py index f0cdf499..dd5b2091 100644 --- a/examples/exchange_client/derivative_exchange_rpc/1_Market.py +++ b/examples/exchange_client/derivative_exchange_rpc/1_Market.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - market = await client.get_derivative_market(market_id=market_id) + market = await client.fetch_derivative_market(market_id=market_id) print(market) diff --git a/examples/exchange_client/derivative_exchange_rpc/20_Binary_Options_Market.py b/examples/exchange_client/derivative_exchange_rpc/20_Binary_Options_Market.py index 6c14d93a..18edbbed 100644 --- a/examples/exchange_client/derivative_exchange_rpc/20_Binary_Options_Market.py +++ b/examples/exchange_client/derivative_exchange_rpc/20_Binary_Options_Market.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x175513943b8677368d138e57bcd6bef53170a0da192e7eaa8c2cd4509b54f8db" - market = await client.get_binary_options_market(market_id=market_id) + market = await client.fetch_binary_options_market(market_id=market_id) print(market) diff --git a/examples/exchange_client/derivative_exchange_rpc/21_Historical_Orders.py b/examples/exchange_client/derivative_exchange_rpc/21_Historical_Orders.py index 9dc22f6f..3e8641ae 100644 --- a/examples/exchange_client/derivative_exchange_rpc/21_Historical_Orders.py +++ b/examples/exchange_client/derivative_exchange_rpc/21_Historical_Orders.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -8,17 +9,17 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" + market_ids = ["0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"] subaccount_id = "0x295639d56c987f0e24d21bb167872b3542a6e05a000000000000000000000000" is_conditional = "false" skip = 10 limit = 3 - orders = await client.get_historical_derivative_orders( - market_id=market_id, + pagination = PaginationOption(skip=skip, limit=limit) + orders = await client.fetch_derivative_orders_history( subaccount_id=subaccount_id, - skip=skip, - limit=limit, + market_ids=market_ids, is_conditional=is_conditional, + pagination=pagination, ) print(orders) diff --git a/examples/exchange_client/derivative_exchange_rpc/22_OrderbooksV2.py b/examples/exchange_client/derivative_exchange_rpc/22_OrderbooksV2.py index 40ae4598..def49ded 100644 --- a/examples/exchange_client/derivative_exchange_rpc/22_OrderbooksV2.py +++ b/examples/exchange_client/derivative_exchange_rpc/22_OrderbooksV2.py @@ -12,7 +12,7 @@ async def main() -> None: "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", "0xd5e4b12b19ecf176e4e14b42944731c27677819d2ed93be4104ad7025529c7ff", ] - orderbooks = await client.get_derivative_orderbooksV2(market_ids=market_ids) + orderbooks = await client.fetch_derivative_orderbooks_v2(market_ids=market_ids) print(orderbooks) diff --git a/examples/exchange_client/derivative_exchange_rpc/23_LiquidablePositions.py b/examples/exchange_client/derivative_exchange_rpc/23_LiquidablePositions.py index e87f67b1..55e76492 100644 --- a/examples/exchange_client/derivative_exchange_rpc/23_LiquidablePositions.py +++ b/examples/exchange_client/derivative_exchange_rpc/23_LiquidablePositions.py @@ -1,8 +1,7 @@ import asyncio -from google.protobuf import json_format - from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -12,18 +11,12 @@ async def main() -> None: market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" skip = 10 limit = 3 - positions = await client.get_derivative_liquidable_positions( + pagination = PaginationOption(skip=skip, limit=limit) + positions = await client.fetch_derivative_liquidable_positions( market_id=market_id, - skip=skip, - limit=limit, - ) - print( - json_format.MessageToJson( - message=positions, - including_default_value_fields=True, - preserving_proto_field_name=True, - ) + pagination=pagination, ) + print(positions) if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/2_Markets.py b/examples/exchange_client/derivative_exchange_rpc/2_Markets.py index 949695e1..b5a5999a 100644 --- a/examples/exchange_client/derivative_exchange_rpc/2_Markets.py +++ b/examples/exchange_client/derivative_exchange_rpc/2_Markets.py @@ -8,9 +8,9 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_status = "active" + market_statuses = ["active"] quote_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" - market = await client.get_derivative_markets(market_status=market_status, quote_denom=quote_denom) + market = await client.fetch_derivative_markets(market_statuses=market_statuses, quote_denom=quote_denom) print(market) diff --git a/examples/exchange_client/derivative_exchange_rpc/4_Orderbook.py b/examples/exchange_client/derivative_exchange_rpc/4_Orderbook.py index 49602701..01443459 100644 --- a/examples/exchange_client/derivative_exchange_rpc/4_Orderbook.py +++ b/examples/exchange_client/derivative_exchange_rpc/4_Orderbook.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - market = await client.get_derivative_orderbook(market_id=market_id) + market = await client.fetch_derivative_orderbook_v2(market_id=market_id) print(market) diff --git a/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py b/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py index 235e75d2..9ae95620 100644 --- a/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py +++ b/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py @@ -24,24 +24,24 @@ def __init__(self, market_id: str): async def load_orderbook_snapshot(async_client: AsyncClient, orderbook: Orderbook): # load the snapshot - res = await async_client.get_derivative_orderbooksV2(market_ids=[orderbook.market_id]) - for snapshot in res.orderbooks: - if snapshot.market_id != orderbook.market_id: + res = await async_client.fetch_derivative_orderbooks_v2(market_ids=[orderbook.market_id]) + for snapshot in res["orderbooks"]: + if snapshot["marketId"] != orderbook.market_id: raise Exception("unexpected snapshot") - orderbook.sequence = int(snapshot.orderbook.sequence) + orderbook.sequence = int(snapshot["orderbook"]["sequence"]) - for buy in snapshot.orderbook.buys: - orderbook.levels["buys"][buy.price] = PriceLevel( - price=Decimal(buy.price), - quantity=Decimal(buy.quantity), - timestamp=buy.timestamp, + for buy in snapshot["orderbook"]["buys"]: + orderbook.levels["buys"][buy["price"]] = PriceLevel( + price=Decimal(buy["price"]), + quantity=Decimal(buy["quantity"]), + timestamp=int(buy["timestamp"]), ) - for sell in snapshot.orderbook.sells: - orderbook.levels["sells"][sell.price] = PriceLevel( - price=Decimal(sell.price), - quantity=Decimal(sell.quantity), - timestamp=sell.timestamp, + for sell in snapshot["orderbook"]["sells"]: + orderbook.levels["sells"][sell["price"]] = PriceLevel( + price=Decimal(sell["price"]), + quantity=Decimal(sell["quantity"]), + timestamp=int(sell["timestamp"]), ) break diff --git a/examples/exchange_client/derivative_exchange_rpc/7_Positions.py b/examples/exchange_client/derivative_exchange_rpc/7_Positions.py index fddd7b3a..e0ffb8b1 100644 --- a/examples/exchange_client/derivative_exchange_rpc/7_Positions.py +++ b/examples/exchange_client/derivative_exchange_rpc/7_Positions.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -17,13 +18,13 @@ async def main() -> None: subaccount_total_positions = False skip = 4 limit = 4 - positions = await client.get_derivative_positions( + pagination = PaginationOption(skip=skip, limit=limit) + positions = await client.fetch_derivative_positions( market_ids=market_ids, - ubaccount_id=subaccount_id, + subaccount_id=subaccount_id, direction=direction, subaccount_total_positions=subaccount_total_positions, - skip=skip, - limit=limit, + pagination=pagination, ) print(positions) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 8e6b3c5c..cef3c829 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -14,6 +14,7 @@ from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi from pyinjective.client.indexer.grpc.indexer_grpc_auction_api import IndexerGrpcAuctionApi +from pyinjective.client.indexer.grpc.indexer_grpc_derivative_api import IndexerGrpcDerivativeApi from pyinjective.client.indexer.grpc.indexer_grpc_insurance_api import IndexerGrpcInsuranceApi from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi from pyinjective.client.indexer.grpc.indexer_grpc_oracle_api import IndexerGrpcOracleApi @@ -187,6 +188,12 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_derivative_api = IndexerGrpcDerivativeApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) self.exchange_insurance_api = IndexerGrpcInsuranceApi( channel=self.exchange_channel, metadata_provider=lambda: self.network.exchange_metadata( @@ -1440,16 +1447,37 @@ async def fetch_spot_subaccount_trades_list( # DerivativeRPC async def get_derivative_market(self, market_id: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_market` instead + """ + warn("This method is deprecated. Use fetch_derivative_market instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.MarketRequest(market_id=market_id) return await self.stubDerivativeExchange.Market(req) + async def fetch_derivative_market(self, market_id: str) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_market(market_id=market_id) + async def get_derivative_markets(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_markets` instead + """ + warn("This method is deprecated. Use fetch_derivative_markets instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.MarketsRequest( market_status=kwargs.get("market_status"), quote_denom=kwargs.get("quote_denom"), ) return await self.stubDerivativeExchange.Markets(req) + async def fetch_derivative_markets( + self, + market_statuses: Optional[List[str]] = None, + quote_denom: Optional[str] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_markets( + market_statuses=market_statuses, + quote_denom=quote_denom, + ) + async def stream_derivative_markets(self, **kwargs): req = derivative_exchange_rpc_pb.StreamMarketRequest(market_ids=kwargs.get("market_ids")) metadata = await self.network.exchange_metadata( @@ -1458,18 +1486,32 @@ async def stream_derivative_markets(self, **kwargs): return self.stubDerivativeExchange.StreamMarket(request=req, metadata=metadata) async def get_derivative_orderbook(self, market_id: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_orderbook_v2` instead + """ + warn("This method is deprecated. Use fetch_derivative_orderbook_v2 instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.OrderbookV2Request(market_id=market_id) return await self.stubDerivativeExchange.OrderbookV2(req) - async def get_derivative_orderbooks(self, market_ids: List): - req = derivative_exchange_rpc_pb.OrderbooksV2Request(market_ids=market_ids) - return await self.stubDerivativeExchange.OrderbooksV2(req) + async def fetch_derivative_orderbook_v2(self, market_id: str) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_orderbook_v2(market_id=market_id) - async def get_derivative_orderbooksV2(self, market_ids: List): + async def get_derivative_orderbooksV2(self, market_ids: List[str]): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_orderbooks_v2` instead + """ + warn("This method is deprecated. Use fetch_derivative_orderbooks_v2 instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.OrderbooksV2Request(market_ids=market_ids) return await self.stubDerivativeExchange.OrderbooksV2(req) + async def fetch_derivative_orderbooks_v2(self, market_ids: List[str]) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_orderbooks_v2(market_ids=market_ids) + async def get_derivative_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_orders` instead + """ + warn("This method is deprecated. Use fetch_derivative_orders instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.OrdersRequest( market_id=market_id, order_side=kwargs.get("order_side"), @@ -1488,7 +1530,37 @@ async def get_derivative_orders(self, market_id: str, **kwargs): ) return await self.stubDerivativeExchange.Orders(req) + async def fetch_derivative_orders( + self, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[str] = None, + is_conditional: Optional[str] = None, + order_type: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_orders( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + is_conditional=is_conditional, + order_type=order_type, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + pagination=pagination, + ) + async def get_historical_derivative_orders(self, market_id: Optional[str] = None, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_orders_history` instead + """ + warn("This method is deprecated. Use fetch_derivative_orders_history instead", DeprecationWarning, stacklevel=2) market_ids = kwargs.get("market_ids", []) if market_id is not None: market_ids.append(market_id) @@ -1514,7 +1586,39 @@ async def get_historical_derivative_orders(self, market_id: Optional[str] = None ) return await self.stubDerivativeExchange.OrdersHistory(req) + async def fetch_derivative_orders_history( + self, + subaccount_id: Optional[str] = None, + market_ids: Optional[List[str]] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + is_conditional: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + active_markets_only: Optional[bool] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_orders_history( + subaccount_id=subaccount_id, + market_ids=market_ids, + order_types=order_types, + direction=direction, + is_conditional=is_conditional, + state=state, + execution_types=execution_types, + trade_id=trade_id, + active_markets_only=active_markets_only, + cid=cid, + pagination=pagination, + ) + async def get_derivative_trades(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_trades` instead + """ + warn("This method is deprecated. Use fetch_derivative_trades instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.TradesRequest( market_id=kwargs.get("market_id"), execution_side=kwargs.get("execution_side"), @@ -1533,6 +1637,30 @@ async def get_derivative_trades(self, **kwargs): ) return await self.stubDerivativeExchange.Trades(req) + async def fetch_derivative_trades( + self, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_trades( + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + pagination=pagination, + ) + async def stream_derivative_orderbook_snapshot(self, market_ids: List[str]): req = derivative_exchange_rpc_pb.StreamOrderbookV2Request(market_ids=market_ids) metadata = await self.network.exchange_metadata( @@ -1592,6 +1720,10 @@ async def stream_derivative_trades(self, **kwargs): return self.stubDerivativeExchange.StreamTrades(request=req, metadata=metadata) async def get_derivative_positions(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_positions` instead + """ + warn("This method is deprecated. Use fetch_derivative_positions instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.PositionsRequest( market_id=kwargs.get("market_id"), market_ids=kwargs.get("market_ids"), @@ -1603,6 +1735,22 @@ async def get_derivative_positions(self, **kwargs): ) return await self.stubDerivativeExchange.Positions(req) + async def fetch_derivative_positions( + self, + market_ids: Optional[List[str]] = None, + subaccount_id: Optional[str] = None, + direction: Optional[str] = None, + subaccount_total_positions: Optional[bool] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_positions( + market_ids=market_ids, + subaccount_id=subaccount_id, + direction=direction, + subaccount_total_positions=subaccount_total_positions, + pagination=pagination, + ) + async def stream_derivative_positions(self, **kwargs): req = derivative_exchange_rpc_pb.StreamPositionsRequest( market_id=kwargs.get("market_id"), @@ -1616,6 +1764,14 @@ async def stream_derivative_positions(self, **kwargs): return self.stubDerivativeExchange.StreamPositions(request=req, metadata=metadata) async def get_derivative_liquidable_positions(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_liquidable_positions` instead + """ + warn( + "This method is deprecated. Use fetch_derivative_liquidable_positions instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.LiquidablePositionsRequest( market_id=kwargs.get("market_id"), skip=kwargs.get("skip"), @@ -1623,7 +1779,25 @@ async def get_derivative_liquidable_positions(self, **kwargs): ) return await self.stubDerivativeExchange.LiquidablePositions(req) + async def fetch_derivative_liquidable_positions( + self, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_liquidable_positions( + market_id=market_id, + pagination=pagination, + ) + async def get_derivative_subaccount_orders(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_subaccount_orders` instead + """ + warn( + "This method is deprecated. Use fetch_derivative_subaccount_orders instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.SubaccountOrdersListRequest( subaccount_id=subaccount_id, market_id=kwargs.get("market_id"), @@ -1632,7 +1806,25 @@ async def get_derivative_subaccount_orders(self, subaccount_id: str, **kwargs): ) return await self.stubDerivativeExchange.SubaccountOrdersList(req) + async def fetch_subaccount_orders_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_subaccount_orders_list( + subaccount_id=subaccount_id, market_id=market_id, pagination=pagination + ) + async def get_derivative_subaccount_trades(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_subaccount_trades` instead + """ + warn( + "This method is deprecated. Use fetch_derivative_subaccount_trades instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.SubaccountTradesListRequest( subaccount_id=subaccount_id, market_id=kwargs.get("market_id"), @@ -1643,7 +1835,27 @@ async def get_derivative_subaccount_trades(self, subaccount_id: str, **kwargs): ) return await self.stubDerivativeExchange.SubaccountTradesList(req) + async def fetch_subaccount_trades_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + execution_type: Optional[str] = None, + direction: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_subaccount_trades_list( + subaccount_id=subaccount_id, + market_id=market_id, + execution_type=execution_type, + direction=direction, + pagination=pagination, + ) + async def get_funding_payments(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_funding_payments` instead + """ + warn("This method is deprecated. Use fetch_funding_payments instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.FundingPaymentsRequest( subaccount_id=subaccount_id, market_id=kwargs.get("market_id"), @@ -1654,7 +1866,21 @@ async def get_funding_payments(self, subaccount_id: str, **kwargs): ) return await self.stubDerivativeExchange.FundingPayments(req) + async def fetch_funding_payments( + self, + market_ids: Optional[List[str]] = None, + subaccount_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_funding_payments( + market_ids=market_ids, subaccount_id=subaccount_id, pagination=pagination + ) + async def get_funding_rates(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_funding_rates` instead + """ + warn("This method is deprecated. Use fetch_funding_rates instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.FundingRatesRequest( market_id=market_id, skip=kwargs.get("skip"), @@ -1663,7 +1889,18 @@ async def get_funding_rates(self, market_id: str, **kwargs): ) return await self.stubDerivativeExchange.FundingRates(req) + async def fetch_funding_rates( + self, + market_id: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_funding_rates(market_id=market_id, pagination=pagination) + async def get_binary_options_markets(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_binary_options_markets` instead + """ + warn("This method is deprecated. Use fetch_binary_options_markets instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.BinaryOptionsMarketsRequest( market_status=kwargs.get("market_status"), quote_denom=kwargs.get("quote_denom"), @@ -1672,10 +1909,29 @@ async def get_binary_options_markets(self, **kwargs): ) return await self.stubDerivativeExchange.BinaryOptionsMarkets(req) + async def fetch_binary_options_markets( + self, + market_status: Optional[str] = None, + quote_denom: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_binary_options_markets( + market_status=market_status, + quote_denom=quote_denom, + pagination=pagination, + ) + async def get_binary_options_market(self, market_id: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_binary_options_market` instead + """ + warn("This method is deprecated. Use fetch_binary_options_market instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.BinaryOptionsMarketRequest(market_id=market_id) return await self.stubDerivativeExchange.BinaryOptionsMarket(req) + async def fetch_binary_options_market(self, market_id: str) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_binary_options_market(market_id=market_id) + # PortfolioRPC async def get_account_portfolio(self, account_address: str): @@ -1738,7 +1994,8 @@ async def _initialize_tokens_and_markets(self): valid_markets = ( market_info for market_info in markets_info - if len(market_info["baseTokenMeta"]["symbol"]) > 0 and len(market_info["quoteTokenMeta"]["symbol"]) > 0 + if len(market_info.get("baseTokenMeta", {}).get("symbol", "")) > 0 + and len(market_info.get("quoteTokenMeta", {}).get("symbol", "")) > 0 ) for market_info in valid_markets: @@ -1782,9 +2039,11 @@ async def _initialize_tokens_and_markets(self): spot_markets[market.id] = market - markets_info = (await self.get_derivative_markets(market_status="active")).markets + markets_info = (await self.fetch_derivative_markets(market_statuses=["active"]))["markets"] valid_markets = ( - market_info for market_info in markets_info if len(market_info["quoteTokenMeta"]["symbol"]) > 0 + market_info + for market_info in markets_info + if len(market_info.get("quoteTokenMeta", {}).get("symbol", "")) > 0 ) for market_info in valid_markets: @@ -1819,7 +2078,7 @@ async def _initialize_tokens_and_markets(self): derivative_markets[market.id] = market - markets_info = (await self.get_binary_options_markets(market_status="active")).markets + markets_info = (await self.fetch_binary_options_markets(market_status="active"))["markets"] for market_info in markets_info: quote_token = tokens_by_denom.get(market_info["quoteDenom"], None) @@ -1839,6 +2098,9 @@ async def _initialize_tokens_and_markets(self): service_provider_fee=Decimal(market_info["serviceProviderFee"]), min_price_tick_size=Decimal(market_info["minPriceTickSize"]), min_quantity_tick_size=Decimal(market_info["minQuantityTickSize"]), + settlement_price=None + if market_info["settlementPrice"] == "" + else Decimal(market_info["settlementPrice"]), ) binary_option_markets[market.id] = market @@ -1848,15 +2110,17 @@ async def _initialize_tokens_and_markets(self): self._derivative_markets = derivative_markets self._binary_option_markets = binary_option_markets - def _token_representation(self, symbol: str, token_meta, denom: str, all_tokens: Dict[str, Token]) -> Token: + def _token_representation( + self, symbol: str, token_meta: Dict[str, Any], denom: str, all_tokens: Dict[str, Token] + ) -> Token: token = Token( - name=token_meta.name, + name=token_meta["name"], symbol=symbol, denom=denom, - address=token_meta.address, - decimals=token_meta.decimals, - logo=token_meta.logo, - updated=token_meta.updated_at, + address=token_meta["address"], + decimals=token_meta["decimals"], + logo=token_meta["logo"], + updated=int(token_meta["updatedAt"]), ) existing_token = all_tokens.get(token.symbol, None) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_derivative_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_derivative_api.py new file mode 100644 index 00000000..a79f9cac --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_derivative_api.py @@ -0,0 +1,291 @@ +from typing import Any, Callable, Dict, List, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.exchange import ( + injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb, + injective_derivative_exchange_rpc_pb2_grpc as exchange_derivative_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcDerivativeApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_derivative_grpc.InjectiveDerivativeExchangeRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_markets( + self, + market_statuses: Optional[List[str]] = None, + quote_denom: Optional[str] = None, + ) -> Dict[str, Any]: + request = exchange_derivative_pb.MarketsRequest( + market_statuses=market_statuses, + quote_denom=quote_denom, + ) + response = await self._execute_call(call=self._stub.Markets, request=request) + + return response + + async def fetch_market(self, market_id: str) -> Dict[str, Any]: + request = exchange_derivative_pb.MarketRequest(market_id=market_id) + response = await self._execute_call(call=self._stub.Market, request=request) + + return response + + async def fetch_binary_options_markets( + self, + market_status: Optional[str] = None, + quote_denom: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.BinaryOptionsMarketsRequest( + market_status=market_status, + quote_denom=quote_denom, + skip=pagination.skip, + limit=pagination.limit, + ) + response = await self._execute_call(call=self._stub.BinaryOptionsMarkets, request=request) + + return response + + async def fetch_binary_options_market(self, market_id: str) -> Dict[str, Any]: + request = exchange_derivative_pb.BinaryOptionsMarketRequest(market_id=market_id) + response = await self._execute_call(call=self._stub.BinaryOptionsMarket, request=request) + + return response + + async def fetch_orderbook_v2(self, market_id: str) -> Dict[str, Any]: + request = exchange_derivative_pb.OrderbookV2Request(market_id=market_id) + response = await self._execute_call(call=self._stub.OrderbookV2, request=request) + + return response + + async def fetch_orderbooks_v2(self, market_ids: List[str]) -> Dict[str, Any]: + request = exchange_derivative_pb.OrderbooksV2Request(market_ids=market_ids) + response = await self._execute_call(call=self._stub.OrderbooksV2, request=request) + + return response + + async def fetch_orders( + self, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[str] = None, + is_conditional: Optional[str] = None, + order_type: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.OrdersRequest( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + is_conditional=is_conditional, + order_type=order_type, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.Orders, request=request) + + return response + + async def fetch_positions( + self, + market_ids: Optional[List[str]] = None, + subaccount_id: Optional[str] = None, + direction: Optional[str] = None, + subaccount_total_positions: Optional[bool] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.PositionsRequest( + market_ids=market_ids, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + direction=direction, + subaccount_total_positions=subaccount_total_positions, + ) + + response = await self._execute_call(call=self._stub.Positions, request=request) + + return response + + async def fetch_liquidable_positions( + self, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.LiquidablePositionsRequest( + market_id=market_id, + skip=pagination.skip, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.LiquidablePositions, request=request) + + return response + + async def fetch_funding_payments( + self, + market_ids: Optional[List[str]] = None, + subaccount_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.FundingPaymentsRequest( + market_ids=market_ids, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + end_time=pagination.end_time, + ) + + response = await self._execute_call(call=self._stub.FundingPayments, request=request) + + return response + + async def fetch_funding_rates( + self, + market_id: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.FundingRatesRequest( + market_id=market_id, + skip=pagination.skip, + limit=pagination.limit, + end_time=pagination.end_time, + ) + + response = await self._execute_call(call=self._stub.FundingRates, request=request) + + return response + + async def fetch_trades( + self, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.TradesRequest( + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.Trades, request=request) + + return response + + async def fetch_subaccount_orders_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.SubaccountOrdersListRequest( + subaccount_id=subaccount_id, + market_id=market_id, + skip=pagination.skip, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.SubaccountOrdersList, request=request) + + return response + + async def fetch_subaccount_trades_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + execution_type: Optional[str] = None, + direction: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.SubaccountTradesListRequest( + subaccount_id=subaccount_id, + market_id=market_id, + execution_type=execution_type, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.SubaccountTradesList, request=request) + + return response + + async def fetch_orders_history( + self, + subaccount_id: Optional[str] = None, + market_ids: Optional[List[str]] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + is_conditional: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + active_markets_only: Optional[bool] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.OrdersHistoryRequest( + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + order_types=order_types, + direction=direction, + start_time=pagination.start_time, + end_time=pagination.end_time, + is_conditional=is_conditional, + state=state, + execution_types=execution_types, + market_ids=market_ids, + trade_id=trade_id, + active_markets_only=active_markets_only, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.OrdersHistory, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/core/market.py b/pyinjective/core/market.py index 228a3a52..3faf21d2 100644 --- a/pyinjective/core/market.py +++ b/pyinjective/core/market.py @@ -125,6 +125,7 @@ class BinaryOptionMarket: service_provider_fee: Decimal min_price_tick_size: Decimal min_quantity_tick_size: Decimal + settlement_price: Optional[Decimal] = None def quantity_to_chain_format(self, human_readable_value: Decimal, special_denom: Optional[Denom] = None) -> Decimal: # Binary option markets do not have a base market to provide the number of decimals diff --git a/tests/client/indexer/configurable_derivative_query_servicer.py b/tests/client/indexer/configurable_derivative_query_servicer.py new file mode 100644 index 00000000..083e431e --- /dev/null +++ b/tests/client/indexer/configurable_derivative_query_servicer.py @@ -0,0 +1,83 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb, + injective_derivative_exchange_rpc_pb2_grpc as exchange_derivative_grpc, +) + + +class ConfigurableDerivativeQueryServicer(exchange_derivative_grpc.InjectiveDerivativeExchangeRPCServicer): + def __init__(self): + super().__init__() + self.markets_responses = deque() + self.market_responses = deque() + self.binary_options_markets_responses = deque() + self.binary_options_market_responses = deque() + self.orderbook_v2_responses = deque() + self.orderbooks_v2_responses = deque() + self.orders_responses = deque() + self.positions_responses = deque() + self.liquidable_positions_responses = deque() + self.funding_payments_responses = deque() + self.funding_rates_responses = deque() + self.trades_responses = deque() + self.subaccount_orders_list_responses = deque() + self.subaccount_trades_list_responses = deque() + self.orders_history_responses = deque() + + async def Markets(self, request: exchange_derivative_pb.MarketsRequest, context=None, metadata=None): + return self.markets_responses.pop() + + async def Market(self, request: exchange_derivative_pb.MarketRequest, context=None, metadata=None): + return self.market_responses.pop() + + async def BinaryOptionsMarkets( + self, request: exchange_derivative_pb.BinaryOptionsMarketsRequest, context=None, metadata=None + ): + return self.binary_options_markets_responses.pop() + + async def BinaryOptionsMarket( + self, request: exchange_derivative_pb.BinaryOptionsMarketRequest, context=None, metadata=None + ): + return self.binary_options_market_responses.pop() + + async def OrderbookV2(self, request: exchange_derivative_pb.OrderbookV2Request, context=None, metadata=None): + return self.orderbook_v2_responses.pop() + + async def OrderbooksV2(self, request: exchange_derivative_pb.OrderbooksV2Request, context=None, metadata=None): + return self.orderbooks_v2_responses.pop() + + async def Orders(self, request: exchange_derivative_pb.OrdersRequest, context=None, metadata=None): + return self.orders_responses.pop() + + async def Positions(self, request: exchange_derivative_pb.PositionsRequest, context=None, metadata=None): + return self.positions_responses.pop() + + async def LiquidablePositions( + self, request: exchange_derivative_pb.LiquidablePositionsRequest, context=None, metadata=None + ): + return self.liquidable_positions_responses.pop() + + async def FundingPayments( + self, request: exchange_derivative_pb.FundingPaymentsRequest, context=None, metadata=None + ): + return self.funding_payments_responses.pop() + + async def FundingRates(self, request: exchange_derivative_pb.FundingRatesRequest, context=None, metadata=None): + return self.funding_rates_responses.pop() + + async def Trades(self, request: exchange_derivative_pb.TradesRequest, context=None, metadata=None): + return self.trades_responses.pop() + + async def SubaccountOrdersList( + self, request: exchange_derivative_pb.SubaccountOrdersListRequest, context=None, metadata=None + ): + return self.subaccount_orders_list_responses.pop() + + async def SubaccountTradesList( + self, request: exchange_derivative_pb.SubaccountTradesListRequest, context=None, metadata=None + ): + return self.subaccount_trades_list_responses.pop() + + async def OrdersHistory(self, request: exchange_derivative_pb.OrdersHistoryRequest, context=None, metadata=None): + return self.orders_history_responses.pop() diff --git a/tests/client/indexer/grpc/test_indexer_grpc_derivative_api.py b/tests/client/indexer/grpc/test_indexer_grpc_derivative_api.py new file mode 100644 index 00000000..4ba28c2b --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_derivative_api.py @@ -0,0 +1,1236 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_derivative_api import IndexerGrpcDerivativeApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb +from tests.client.indexer.configurable_derivative_query_servicer import ConfigurableDerivativeQueryServicer + + +@pytest.fixture +def derivative_servicer(): + return ConfigurableDerivativeQueryServicer() + + +class TestIndexerGrpcDerivativeApi: + @pytest.mark.asyncio + async def test_fetch_markets( + self, + derivative_servicer, + ): + quote_token_meta = exchange_derivative_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + perpetual_market_info = exchange_derivative_pb.PerpetualMarketInfo( + hourly_funding_rate_cap="0.000625", + hourly_interest_rate="0.00000416666", + next_funding_timestamp=1700064000, + funding_interval=3600, + ) + perpetual_market_funding = exchange_derivative_pb.PerpetualMarketFunding( + cumulative_funding="-82680.076492986572881307", + cumulative_price="-78.41752505919454668", + last_timestamp=1700004260, + ) + + market = exchange_derivative_pb.DerivativeMarketInfo( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + market_status="active", + ticker="INJ/USDT PERP", + oracle_base="0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3", + oracle_quote="0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588", + oracle_type="pyth", + oracle_scale_factor=6, + initial_margin_ratio="0.05", + maintenance_margin_ratio="0.02", + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + is_perpetual=True, + min_price_tick_size="100", + min_quantity_tick_size="0.0001", + perpetual_market_info=perpetual_market_info, + perpetual_market_funding=perpetual_market_funding, + ) + + derivative_servicer.markets_responses.append( + exchange_derivative_pb.MarketsResponse( + markets=[market], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_markets = await api.fetch_markets( + market_statuses=[market.market_status], + quote_denom=market.quote_denom, + ) + expected_markets = { + "markets": [ + { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "oracleBase": market.oracle_base, + "oracleQuote": market.oracle_quote, + "oracleType": market.oracle_type, + "oracleScaleFactor": market.oracle_scale_factor, + "initialMarginRatio": market.initial_margin_ratio, + "maintenanceMarginRatio": market.maintenance_margin_ratio, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "makerFeeRate": market.maker_fee_rate, + "takerFeeRate": market.taker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "isPerpetual": market.is_perpetual, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + "perpetualMarketInfo": { + "hourlyFundingRateCap": perpetual_market_info.hourly_funding_rate_cap, + "hourlyInterestRate": str(perpetual_market_info.hourly_interest_rate), + "nextFundingTimestamp": str(perpetual_market_info.next_funding_timestamp), + "fundingInterval": str(perpetual_market_info.funding_interval), + }, + "perpetualMarketFunding": { + "cumulativeFunding": perpetual_market_funding.cumulative_funding, + "cumulativePrice": perpetual_market_funding.cumulative_price, + "lastTimestamp": str(perpetual_market_funding.last_timestamp), + }, + } + ] + } + + assert result_markets == expected_markets + + @pytest.mark.asyncio + async def test_fetch_market( + self, + derivative_servicer, + ): + quote_token_meta = exchange_derivative_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + perpetual_market_info = exchange_derivative_pb.PerpetualMarketInfo( + hourly_funding_rate_cap="0.000625", + hourly_interest_rate="0.00000416666", + next_funding_timestamp=1700064000, + funding_interval=3600, + ) + perpetual_market_funding = exchange_derivative_pb.PerpetualMarketFunding( + cumulative_funding="-82680.076492986572881307", + cumulative_price="-78.41752505919454668", + last_timestamp=1700004260, + ) + + market = exchange_derivative_pb.DerivativeMarketInfo( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + market_status="active", + ticker="INJ/USDT PERP", + oracle_base="0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3", + oracle_quote="0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588", + oracle_type="pyth", + oracle_scale_factor=6, + initial_margin_ratio="0.05", + maintenance_margin_ratio="0.02", + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + is_perpetual=True, + min_price_tick_size="100", + min_quantity_tick_size="0.0001", + perpetual_market_info=perpetual_market_info, + perpetual_market_funding=perpetual_market_funding, + ) + + derivative_servicer.market_responses.append( + exchange_derivative_pb.MarketResponse( + market=market, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_market = await api.fetch_market(market_id=market.market_id) + expected_market = { + "market": { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "oracleBase": market.oracle_base, + "oracleQuote": market.oracle_quote, + "oracleType": market.oracle_type, + "oracleScaleFactor": market.oracle_scale_factor, + "initialMarginRatio": market.initial_margin_ratio, + "maintenanceMarginRatio": market.maintenance_margin_ratio, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "makerFeeRate": market.maker_fee_rate, + "takerFeeRate": market.taker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "isPerpetual": market.is_perpetual, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + "perpetualMarketInfo": { + "hourlyFundingRateCap": perpetual_market_info.hourly_funding_rate_cap, + "hourlyInterestRate": str(perpetual_market_info.hourly_interest_rate), + "nextFundingTimestamp": str(perpetual_market_info.next_funding_timestamp), + "fundingInterval": str(perpetual_market_info.funding_interval), + }, + "perpetualMarketFunding": { + "cumulativeFunding": perpetual_market_funding.cumulative_funding, + "cumulativePrice": perpetual_market_funding.cumulative_price, + "lastTimestamp": str(perpetual_market_funding.last_timestamp), + }, + } + } + + assert result_market == expected_market + + @pytest.mark.asyncio + async def test_fetch_binary_options_markets( + self, + derivative_servicer, + ): + quote_token_meta = exchange_derivative_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + + market = exchange_derivative_pb.BinaryOptionsMarketInfo( + market_id="0xaea3b04b88ad7972b6afcd676791eaa1872a8cf5ab7c5be93da755fd7fac9196", + market_status="active", + ticker="Long-Lived 7/8/22 1", + oracle_symbol="Long-Lived 7/8/22 1", + oracle_provider="Frontrunner", + oracle_type="provider", + oracle_scale_factor=6, + expiration_timestamp=1657311861, + settlement_timestamp=1657311862, + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + min_price_tick_size="0.01", + min_quantity_tick_size="1", + settlement_price="1000", + ) + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.binary_options_markets_responses.append( + exchange_derivative_pb.BinaryOptionsMarketsResponse(markets=[market], paging=paging) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_markets = await api.fetch_binary_options_markets( + market_status=market.market_status, + quote_denom=market.quote_denom, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_markets = { + "markets": [ + { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "oracleSymbol": market.oracle_symbol, + "oracleProvider": market.oracle_provider, + "oracleType": market.oracle_type, + "oracleScaleFactor": market.oracle_scale_factor, + "expirationTimestamp": str(market.expiration_timestamp), + "settlementTimestamp": str(market.settlement_timestamp), + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "makerFeeRate": market.maker_fee_rate, + "takerFeeRate": market.taker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + "settlementPrice": market.settlement_price, + } + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_markets == expected_markets + + @pytest.mark.asyncio + async def test_fetch_binary_options_market( + self, + derivative_servicer, + ): + quote_token_meta = exchange_derivative_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + + market = exchange_derivative_pb.BinaryOptionsMarketInfo( + market_id="0xaea3b04b88ad7972b6afcd676791eaa1872a8cf5ab7c5be93da755fd7fac9196", + market_status="active", + ticker="Long-Lived 7/8/22 1", + oracle_symbol="Long-Lived 7/8/22 1", + oracle_provider="Frontrunner", + oracle_type="provider", + oracle_scale_factor=6, + expiration_timestamp=1657311861, + settlement_timestamp=1657311862, + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + min_price_tick_size="0.01", + min_quantity_tick_size="1", + settlement_price="1000", + ) + + derivative_servicer.binary_options_market_responses.append( + exchange_derivative_pb.BinaryOptionsMarketResponse(market=market) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_markets = await api.fetch_binary_options_market(market_id=market.market_id) + expected_markets = { + "market": { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "oracleSymbol": market.oracle_symbol, + "oracleProvider": market.oracle_provider, + "oracleType": market.oracle_type, + "oracleScaleFactor": market.oracle_scale_factor, + "expirationTimestamp": str(market.expiration_timestamp), + "settlementTimestamp": str(market.settlement_timestamp), + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "makerFeeRate": market.maker_fee_rate, + "takerFeeRate": market.taker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + "settlementPrice": market.settlement_price, + } + } + + assert result_markets == expected_markets + + @pytest.mark.asyncio + async def test_fetch_orderbook_v2( + self, + derivative_servicer, + ): + buy = exchange_derivative_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_derivative_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_derivative_pb.DerivativeLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + derivative_servicer.orderbook_v2_responses.append( + exchange_derivative_pb.OrderbookV2Response( + orderbook=orderbook, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orderbook = await api.fetch_orderbook_v2( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" + ) + expected_orderbook = { + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + } + } + + assert result_orderbook == expected_orderbook + + @pytest.mark.asyncio + async def test_fetch_orderbooks_v2( + self, + derivative_servicer, + ): + buy = exchange_derivative_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_derivative_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_derivative_pb.DerivativeLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + single_orderbook = exchange_derivative_pb.SingleDerivativeLimitOrderbookV2( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + orderbook=orderbook, + ) + + derivative_servicer.orderbooks_v2_responses.append( + exchange_derivative_pb.OrderbooksV2Response( + orderbooks=[single_orderbook], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orderbook = await api.fetch_orderbooks_v2(market_ids=[single_orderbook.market_id]) + expected_orderbook = { + "orderbooks": [ + { + "marketId": single_orderbook.market_id, + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + }, + } + ] + } + + assert result_orderbook == expected_orderbook + + @pytest.mark.asyncio + async def test_fetch_orders( + self, + derivative_servicer, + ): + order = exchange_derivative_pb.DerivativeLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + is_reduce_only=False, + margin="2280000000", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + order_number=0, + order_type="", + is_conditional=False, + trigger_at=0, + placed_order_hash="", + execution_type="", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.orders_responses.append( + exchange_derivative_pb.OrdersResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_orders( + market_ids=[order.market_id], + order_side=order.order_side, + subaccount_id=order.subaccount_id, + is_conditional="true" if order.is_conditional else "false", + order_type=order.order_type, + include_inactive=True, + subaccount_total_orders=True, + trade_id="7959737_3_0", + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "isReduceOnly": order.is_reduce_only, + "margin": order.margin, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "orderNumber": str(order.order_number), + "orderType": order.order_type, + "isConditional": order.is_conditional, + "triggerAt": str(order.trigger_at), + "placedOrderHash": order.placed_order_hash, + "executionType": order.execution_type, + "txHash": order.tx_hash, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_positions( + self, + derivative_servicer, + ): + position = exchange_derivative_pb.DerivativePosition( + ticker="INJ/USDT PERP", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x1383dabde57e5aed55960ee43e158ae7118057d3000000000000000000000000", + direction="short", + quantity="0.070294765766186502", + entry_price="15980281.340438795311756847", + margin="561065.540974", + liquidation_price="23492052.224777", + mark_price="16197000", + aggregate_reduce_only_quantity="0", + updated_at=1700161202147, + created_at=-62135596800000, + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.positions_responses.append( + exchange_derivative_pb.PositionsResponse( + positions=[position], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_positions( + market_ids=[position.market_id], + subaccount_id=position.subaccount_id, + direction=position.direction, + subaccount_total_positions=True, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_orders = { + "positions": [ + { + "ticker": position.ticker, + "marketId": position.market_id, + "subaccountId": position.subaccount_id, + "direction": position.direction, + "quantity": position.quantity, + "entryPrice": position.entry_price, + "margin": position.margin, + "liquidationPrice": position.liquidation_price, + "markPrice": position.mark_price, + "aggregateReduceOnlyQuantity": position.aggregate_reduce_only_quantity, + "createdAt": str(position.created_at), + "updatedAt": str(position.updated_at), + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_liquidable_positions( + self, + derivative_servicer, + ): + position = exchange_derivative_pb.DerivativePosition( + ticker="INJ/USDT PERP", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x1383dabde57e5aed55960ee43e158ae7118057d3000000000000000000000000", + direction="short", + quantity="0.070294765766186502", + entry_price="15980281.340438795311756847", + margin="561065.540974", + liquidation_price="23492052.224777", + mark_price="16197000", + aggregate_reduce_only_quantity="0", + updated_at=1700161202147, + created_at=-62135596800000, + ) + + derivative_servicer.liquidable_positions_responses.append( + exchange_derivative_pb.LiquidablePositionsResponse(positions=[position]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_liquidable_positions( + market_id=position.market_id, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_orders = { + "positions": [ + { + "ticker": position.ticker, + "marketId": position.market_id, + "subaccountId": position.subaccount_id, + "direction": position.direction, + "quantity": position.quantity, + "entryPrice": position.entry_price, + "margin": position.margin, + "liquidationPrice": position.liquidation_price, + "markPrice": position.mark_price, + "aggregateReduceOnlyQuantity": position.aggregate_reduce_only_quantity, + "createdAt": str(position.created_at), + "updatedAt": str(position.updated_at), + }, + ] + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_funding_payments( + self, + derivative_servicer, + ): + payment = exchange_derivative_pb.FundingPayment( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x1383dabde57e5aed55960ee43e158ae7118057d3000000000000000000000000", + amount="0.018466", + timestamp=1700186400645, + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.funding_payments_responses.append( + exchange_derivative_pb.FundingPaymentsResponse( + payments=[payment], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_funding_payments( + market_ids=[payment.market_id], + subaccount_id=payment.subaccount_id, + pagination=PaginationOption( + skip=0, + limit=100, + end_time=1699744939364, + ), + ) + expected_orders = { + "payments": [ + { + "marketId": payment.market_id, + "subaccountId": payment.subaccount_id, + "amount": payment.amount, + "timestamp": str(payment.timestamp), + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_funding_rates( + self, + derivative_servicer, + ): + funding_rate = exchange_derivative_pb.FundingRate( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + rate="0.000004", + timestamp=1700186400645, + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.funding_rates_responses.append( + exchange_derivative_pb.FundingRatesResponse( + funding_rates=[funding_rate], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_funding_rates( + market_id=funding_rate.market_id, + pagination=PaginationOption( + skip=0, + limit=100, + end_time=1699744939364, + ), + ) + expected_orders = { + "fundingRates": [ + { + "marketId": funding_rate.market_id, + "rate": funding_rate.rate, + "timestamp": str(funding_rate.timestamp), + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_trades( + self, + derivative_servicer, + ): + position_delta = exchange_derivative_pb.PositionDelta( + trade_direction="buy", + execution_price="13945600", + execution_quantity="5", + execution_margin="69728000", + ) + + trade = exchange_derivative_pb.DerivativeTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + trade_execution_type="limitMatchNewOrder", + is_liquidation=False, + position_delta=position_delta, + payout="0", + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.trades_responses.append( + exchange_derivative_pb.TradesResponse( + trades=[trade], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_trades = await api.fetch_trades( + market_ids=[trade.market_id], + subaccount_ids=[trade.subaccount_id], + execution_side=trade.execution_side, + direction=position_delta.trade_direction, + execution_types=[trade.trade_execution_type], + trade_id=trade.trade_id, + account_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + cid=trade.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_trades = { + "trades": [ + { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "isLiquidation": trade.is_liquidation, + "positionDelta": { + "tradeDirection": position_delta.trade_direction, + "executionPrice": position_delta.execution_price, + "executionQuantity": position_delta.execution_quantity, + "executionMargin": position_delta.execution_margin, + }, + "payout": trade.payout, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_trades == expected_trades + + @pytest.mark.asyncio + async def test_fetch_subaccount_orders_list( + self, + derivative_servicer, + ): + order = exchange_derivative_pb.DerivativeLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + is_reduce_only=False, + margin="2280000000", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + order_number=0, + order_type="", + is_conditional=False, + trigger_at=0, + placed_order_hash="", + execution_type="", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.subaccount_orders_list_responses.append( + exchange_derivative_pb.SubaccountOrdersListResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_subaccount_orders_list( + subaccount_id=order.subaccount_id, + market_id=order.market_id, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "isReduceOnly": order.is_reduce_only, + "margin": order.margin, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "orderNumber": str(order.order_number), + "orderType": order.order_type, + "isConditional": order.is_conditional, + "triggerAt": str(order.trigger_at), + "placedOrderHash": order.placed_order_hash, + "executionType": order.execution_type, + "txHash": order.tx_hash, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_subaccount_trades_list( + self, + derivative_servicer, + ): + position_delta = exchange_derivative_pb.PositionDelta( + trade_direction="buy", + execution_price="13945600", + execution_quantity="5", + execution_margin="69728000", + ) + + trade = exchange_derivative_pb.DerivativeTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + trade_execution_type="limitMatchNewOrder", + is_liquidation=False, + position_delta=position_delta, + payout="0", + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + derivative_servicer.subaccount_trades_list_responses.append( + exchange_derivative_pb.SubaccountTradesListResponse( + trades=[trade], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_trades = await api.fetch_subaccount_trades_list( + subaccount_id=trade.subaccount_id, + market_id=trade.market_id, + execution_type=trade.trade_execution_type, + direction=position_delta.trade_direction, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_trades = { + "trades": [ + { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "isLiquidation": trade.is_liquidation, + "positionDelta": { + "tradeDirection": position_delta.trade_direction, + "executionPrice": position_delta.execution_price, + "executionQuantity": position_delta.execution_quantity, + "executionMargin": position_delta.execution_margin, + }, + "payout": trade.payout, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + ], + } + + assert result_trades == expected_trades + + @pytest.mark.asyncio + async def test_fetch_orders_history( + self, + derivative_servicer, + ): + order = exchange_derivative_pb.DerivativeOrderHistory( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + is_active=True, + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + execution_type="limit", + order_type="buy_po", + price="0.000000000017541", + trigger_price="0", + quantity="50955000000000000000", + filled_quantity="1000000000000000", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + is_reduce_only=False, + direction="buy", + is_conditional=False, + trigger_at=0, + placed_order_hash="", + margin="2280000000", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.orders_history_responses.append( + exchange_derivative_pb.OrdersHistoryResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_orders_history( + subaccount_id=order.subaccount_id, + market_ids=[order.market_id], + order_types=[order.order_type], + direction=order.direction, + is_conditional="true" if order.is_conditional else "false", + state=order.state, + execution_types=[order.execution_type], + trade_id="8662464_1_0", + active_markets_only=True, + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "marketId": order.market_id, + "isActive": order.is_active, + "subaccountId": order.subaccount_id, + "executionType": order.execution_type, + "orderType": order.order_type, + "price": order.price, + "triggerPrice": order.trigger_price, + "quantity": order.quantity, + "filledQuantity": order.filled_quantity, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "isReduceOnly": order.is_reduce_only, + "direction": order.direction, + "isConditional": order.is_conditional, + "triggerAt": str(order.trigger_at), + "placedOrderHash": order.placed_order_hash, + "margin": order.margin, + "txHash": order.tx_hash, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/rpc_fixtures/configurable_servicers.py b/tests/rpc_fixtures/configurable_servicers.py deleted file mode 100644 index 8c46dd2d..00000000 --- a/tests/rpc_fixtures/configurable_servicers.py +++ /dev/null @@ -1,29 +0,0 @@ -from collections import deque - -from pyinjective.proto.exchange import injective_derivative_exchange_rpc_pb2, injective_spot_exchange_rpc_pb2 -from pyinjective.proto.exchange.injective_derivative_exchange_rpc_pb2_grpc import InjectiveDerivativeExchangeRPCServicer -from pyinjective.proto.exchange.injective_spot_exchange_rpc_pb2_grpc import InjectiveSpotExchangeRPCServicer - - -class ConfigurableInjectiveSpotExchangeRPCServicer(InjectiveSpotExchangeRPCServicer): - def __init__(self): - super().__init__() - self.markets_queue = deque() - - async def Markets(self, request: injective_spot_exchange_rpc_pb2.MarketsRequest, context=None): - return self.markets_queue.pop() - - -class ConfigurableInjectiveDerivativeExchangeRPCServicer(InjectiveDerivativeExchangeRPCServicer): - def __init__(self): - super().__init__() - self.markets_queue = deque() - self.binary_option_markets_queue = deque() - - async def Markets(self, request: injective_derivative_exchange_rpc_pb2.MarketsRequest, context=None): - return self.markets_queue.pop() - - async def BinaryOptionsMarkets( - self, request: injective_derivative_exchange_rpc_pb2.BinaryOptionsMarketsRequest, context=None - ): - return self.binary_option_markets_queue.pop() diff --git a/tests/test_async_client.py b/tests/test_async_client.py index ac18c6f5..7780e088 100644 --- a/tests/test_async_client.py +++ b/tests/test_async_client.py @@ -5,10 +5,8 @@ from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.proto.exchange import injective_derivative_exchange_rpc_pb2, injective_spot_exchange_rpc_pb2 -from tests.rpc_fixtures.configurable_servicers import ( - ConfigurableInjectiveDerivativeExchangeRPCServicer, - ConfigurableInjectiveSpotExchangeRPCServicer, -) +from tests.client.indexer.configurable_derivative_query_servicer import ConfigurableDerivativeQueryServicer +from tests.client.indexer.configurable_spot_query_servicer import ConfigurableSpotQueryServicer from tests.rpc_fixtures.markets_fixtures import ape_token_meta # noqa: F401 from tests.rpc_fixtures.markets_fixtures import ape_usdt_spot_market_meta # noqa: F401 from tests.rpc_fixtures.markets_fixtures import btc_usdt_perp_market_meta # noqa: F401 @@ -24,12 +22,12 @@ @pytest.fixture def spot_servicer(): - return ConfigurableInjectiveSpotExchangeRPCServicer() + return ConfigurableSpotQueryServicer() @pytest.fixture def derivative_servicer(): - return ConfigurableInjectiveDerivativeExchangeRPCServicer() + return ConfigurableDerivativeQueryServicer() class TestAsyncClient: @@ -81,15 +79,15 @@ async def test_initialize_tokens_and_markets( btc_usdt_perp_market_meta, first_match_bet_market_meta, ): - spot_servicer.markets_queue.append( + spot_servicer.markets_responses.append( injective_spot_exchange_rpc_pb2.MarketsResponse( markets=[inj_usdt_spot_market_meta, ape_usdt_spot_market_meta] ) ) - derivative_servicer.markets_queue.append( + derivative_servicer.markets_responses.append( injective_derivative_exchange_rpc_pb2.MarketsResponse(markets=[btc_usdt_perp_market_meta]) ) - derivative_servicer.binary_option_markets_queue.append( + derivative_servicer.binary_options_markets_responses.append( injective_derivative_exchange_rpc_pb2.BinaryOptionsMarketsResponse(markets=[first_match_bet_market_meta]) ) @@ -98,8 +96,8 @@ async def test_initialize_tokens_and_markets( insecure=False, ) - client.stubSpotExchange = spot_servicer - client.stubDerivativeExchange = derivative_servicer + client.exchange_spot_api._stub = spot_servicer + client.exchange_derivative_api._stub = derivative_servicer await client._initialize_tokens_and_markets() diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 4bbf2cab..0d6e7bc0 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -10,6 +10,7 @@ from pyinjective.proto.exchange import ( injective_accounts_rpc_pb2 as exchange_accounts_pb, injective_auction_rpc_pb2 as exchange_auction_pb, + injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb, injective_insurance_rpc_pb2 as exchange_insurance_pb, injective_meta_rpc_pb2 as exchange_meta_pb, injective_oracle_rpc_pb2 as exchange_oracle_pb, @@ -21,6 +22,7 @@ from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer from tests.client.indexer.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer +from tests.client.indexer.configurable_derivative_query_servicer import ConfigurableDerivativeQueryServicer from tests.client.indexer.configurable_insurance_query_servicer import ConfigurableInsuranceQueryServicer from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer @@ -53,6 +55,11 @@ def bank_servicer(): return ConfigurableBankQueryServicer() +@pytest.fixture +def derivative_servicer(): + return ConfigurableDerivativeQueryServicer() + + @pytest.fixture def insurance_servicer(): return ConfigurableInsuranceQueryServicer() @@ -945,3 +952,286 @@ async def test_stream_historical_spot_orders_deprecation_warning( assert ( str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_orders_history_updates instead" ) + + @pytest.mark.asyncio + async def test_get_derivative_markets_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.markets_responses.append(exchange_derivative_pb.MarketsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_markets() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_markets instead" + + @pytest.mark.asyncio + async def test_get_derivative_market_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.market_responses.append(exchange_derivative_pb.MarketResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_market(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_market instead" + + @pytest.mark.asyncio + async def test_get_binary_options_markets_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.binary_options_markets_responses.append( + exchange_derivative_pb.BinaryOptionsMarketsResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.get_binary_options_markets() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_binary_options_markets instead" + + @pytest.mark.asyncio + async def test_get_binary_options_market_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.binary_options_market_responses.append(exchange_derivative_pb.BinaryOptionsMarketResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_binary_options_market(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_binary_options_market instead" + + @pytest.mark.asyncio + async def test_get_derivative_orderbook_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.orderbook_v2_responses.append(exchange_derivative_pb.OrderbookV2Request()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_orderbook(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orderbook_v2 instead" + + @pytest.mark.asyncio + async def test_get_derivative_orderbooksV2_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.orderbooks_v2_responses.append(exchange_derivative_pb.OrderbooksV2Request()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_orderbooksV2(market_ids=[]) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orderbooks_v2 instead" + + @pytest.mark.asyncio + async def test_get_derivative_orders_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.orders_responses.append(exchange_derivative_pb.OrdersResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_orders(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orders instead" + + @pytest.mark.asyncio + async def test_get_derivative_positions_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.positions_responses.append(exchange_derivative_pb.PositionsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_positions() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_positions instead" + + @pytest.mark.asyncio + async def test_get_derivative_liquidable_positions_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.liquidable_positions_responses.append(exchange_derivative_pb.LiquidablePositionsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_liquidable_positions() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_liquidable_positions instead" + ) + + @pytest.mark.asyncio + async def test_get_funding_payments_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.funding_payments_responses.append(exchange_derivative_pb.FundingPaymentsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_funding_payments(subaccount_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_funding_payments instead" + + @pytest.mark.asyncio + async def test_get_funding_rates_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.funding_rates_responses.append(exchange_derivative_pb.FundingRatesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_funding_rates(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_funding_rates instead" + + @pytest.mark.asyncio + async def test_get_derivative_trades_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.trades_responses.append(exchange_derivative_pb.TradesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_trades() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_trades instead" + + @pytest.mark.asyncio + async def test_get_derivative_subaccount_orders_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.subaccount_orders_list_responses.append( + exchange_derivative_pb.SubaccountOrdersListResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_subaccount_orders(subaccount_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_subaccount_orders instead" + ) + + @pytest.mark.asyncio + async def test_get_derivative_subaccount_trades_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.subaccount_trades_list_responses.append( + exchange_derivative_pb.SubaccountTradesListResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_subaccount_trades(subaccount_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_subaccount_trades instead" + ) + + @pytest.mark.asyncio + async def test_get_historical_derivative_orders_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.orders_history_responses.append(exchange_derivative_pb.OrdersHistoryResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_historical_derivative_orders() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orders_history instead" From 0edbb81408042e110590e6de4fb9a993e059838c Mon Sep 17 00:00:00 2001 From: abel Date: Mon, 27 Nov 2023 13:01:28 -0300 Subject: [PATCH 20/27] (feat) Added low level API component for exchange derivative streams. Added unit tests for the new functionality. Included new functions in AsyncClient using the low level API components, and marked the old functions as deprecated --- .../10_StreamHistoricalOrders.py | 30 +- .../12_StreamTrades.py | 35 +- .../14_SubaccountTradesList.py | 2 +- .../derivative_exchange_rpc/3_StreamMarket.py | 29 +- .../5_StreamOrderbooks.py | 32 +- .../6_StreamOrderbookUpdate.py | 138 ++-- .../9_StreamPositions.py | 35 +- pyinjective/async_client.py | 214 +++++- .../indexer_grpc_derivative_stream.py | 196 +++++ .../configurable_derivative_query_servicer.py | 44 ++ .../test_indexer_grpc_derivative_stream.py | 709 ++++++++++++++++++ .../test_async_client_deprecation_warnings.py | 140 +++- 12 files changed, 1521 insertions(+), 83 deletions(-) create mode 100644 pyinjective/client/indexer/grpc_stream/indexer_grpc_derivative_stream.py create mode 100644 tests/client/indexer/stream_grpc/test_indexer_grpc_derivative_stream.py diff --git a/examples/exchange_client/derivative_exchange_rpc/10_StreamHistoricalOrders.py b/examples/exchange_client/derivative_exchange_rpc/10_StreamHistoricalOrders.py index b58448d2..140639fe 100644 --- a/examples/exchange_client/derivative_exchange_rpc/10_StreamHistoricalOrders.py +++ b/examples/exchange_client/derivative_exchange_rpc/10_StreamHistoricalOrders.py @@ -1,17 +1,41 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def order_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to derivative orders history updates ({exception})") + + +def stream_closed_processor(): + print("The derivative orders history updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - orders = await client.stream_historical_derivative_orders(market_id=market_id) - async for order in orders: - print(order) + + task = asyncio.get_event_loop().create_task( + client.listen_derivative_orders_history_updates( + callback=order_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + market_id=market_id, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/12_StreamTrades.py b/examples/exchange_client/derivative_exchange_rpc/12_StreamTrades.py index 8d6246bb..9ccf11de 100644 --- a/examples/exchange_client/derivative_exchange_rpc/12_StreamTrades.py +++ b/examples/exchange_client/derivative_exchange_rpc/12_StreamTrades.py @@ -1,21 +1,46 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def market_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to derivative trades updates ({exception})") + + +def stream_closed_processor(): + print("The derivative trades updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) market_ids = [ "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", - "0xd5e4b12b19ecf176e4e14b42944731c27677819d2ed93be4104ad7025529c7ff", + "0x70bc8d7feab38b23d5fdfb12b9c3726e400c265edbcbf449b6c80c31d63d3a02", ] - subaccount_id = "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000" - trades = await client.stream_derivative_trades(market_id=market_ids[0], subaccount_id=subaccount_id) - async for trade in trades: - print(trade) + subaccount_ids = ["0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000"] + + task = asyncio.get_event_loop().create_task( + client.listen_derivative_trades_updates( + callback=market_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py b/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py index c1e703e3..ed90a456 100644 --- a/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py +++ b/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py @@ -16,7 +16,7 @@ async def main() -> None: skip = 10 limit = 2 pagination = PaginationOption(skip=skip, limit=limit) - trades = await client.fetch_subaccount_trades_list( + trades = await client.fetch_derivative_subaccount_trades_list( subaccount_id=subaccount_id, market_id=market_id, execution_type=execution_type, diff --git a/examples/exchange_client/derivative_exchange_rpc/3_StreamMarket.py b/examples/exchange_client/derivative_exchange_rpc/3_StreamMarket.py index b5b7cbb0..d8663ba2 100644 --- a/examples/exchange_client/derivative_exchange_rpc/3_StreamMarket.py +++ b/examples/exchange_client/derivative_exchange_rpc/3_StreamMarket.py @@ -1,16 +1,39 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def market_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to derivative markets updates ({exception})") + + +def stream_closed_processor(): + print("The derivative markets updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - markets = await client.stream_derivative_markets() - async for market in markets: - print(market) + + task = asyncio.get_event_loop().create_task( + client.listen_derivative_market_updates( + callback=market_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/5_StreamOrderbooks.py b/examples/exchange_client/derivative_exchange_rpc/5_StreamOrderbooks.py index ab9c5927..e2044eae 100644 --- a/examples/exchange_client/derivative_exchange_rpc/5_StreamOrderbooks.py +++ b/examples/exchange_client/derivative_exchange_rpc/5_StreamOrderbooks.py @@ -1,17 +1,41 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def orderbook_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to derivative orderbook snapshots ({exception})") + + +def stream_closed_processor(): + print("The derivative orderbook snapshots stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - markets = await client.stream_derivative_orderbook_snapshot(market_ids=[market_id]) - async for market in markets: - print(market) + market_ids = ["0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"] + + task = asyncio.get_event_loop().create_task( + client.listen_derivative_orderbook_snapshots( + market_ids=market_ids, + callback=orderbook_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py b/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py index 9ae95620..93b00a0a 100644 --- a/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py +++ b/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py @@ -1,10 +1,21 @@ import asyncio from decimal import Decimal +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to derivative orderbook updates ({exception})") + + +def stream_closed_processor(): + print("The derivative orderbook updates stream has been closed") + + class PriceLevel: def __init__(self, price: Decimal, quantity: Decimal, timestamp: int): self.price = price @@ -53,74 +64,91 @@ async def main() -> None: market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" orderbook = Orderbook(market_id=market_id) + updates_queue = asyncio.Queue() + tasks = [] + + async def queue_event(event: Dict[str, Any]): + await updates_queue.put(event) # start getting price levels updates - stream = await async_client.stream_derivative_orderbook_update(market_ids=[market_id]) - first_update = None - async for update in stream: - first_update = update.orderbook_level_updates - break + task = asyncio.get_event_loop().create_task( + async_client.listen_derivative_orderbook_updates( + market_ids=[market_id], + callback=queue_event, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + tasks.append(task) # load the snapshot once we are already receiving updates, so we don't miss any await load_orderbook_snapshot(async_client=async_client, orderbook=orderbook) - # start consuming updates again to process them - apply_orderbook_update(orderbook, first_update) - async for update in stream: - apply_orderbook_update(orderbook, update.orderbook_level_updates) + task = asyncio.get_event_loop().create_task( + apply_orderbook_update(orderbook=orderbook, updates_queue=updates_queue) + ) + tasks.append(task) + await asyncio.sleep(delay=60) + for task in tasks: + task.cancel() -def apply_orderbook_update(orderbook: Orderbook, updates): - # discard old updates - if updates.sequence <= orderbook.sequence: - return - print(" * * * * * * * * * * * * * * * * * * *") +async def apply_orderbook_update(orderbook: Orderbook, updates_queue: asyncio.Queue): + while True: + updates = await updates_queue.get() + update = updates["orderbookLevelUpdates"] - # ensure we have not missed any update - if updates.sequence > (orderbook.sequence + 1): - raise Exception( - "missing orderbook update events from stream, must restart: {} vs {}".format( - updates.sequence, (orderbook.sequence + 1) - ) - ) + # discard updates older than the snapshot + if int(update["sequence"]) <= orderbook.sequence: + return - print("updating orderbook with updates at sequence {}".format(updates.sequence)) + print(" * * * * * * * * * * * * * * * * * * *") - # update orderbook - orderbook.sequence = updates.sequence - for direction, levels in {"buys": updates.buys, "sells": updates.sells}.items(): - for level in levels: - if level.is_active: - # upsert level - orderbook.levels[direction][level.price] = PriceLevel( - price=Decimal(level.price), quantity=Decimal(level.quantity), timestamp=level.timestamp + # ensure we have not missed any update + if int(update["sequence"]) > (orderbook.sequence + 1): + raise Exception( + "missing orderbook update events from stream, must restart: {} vs {}".format( + update["sequence"], (orderbook.sequence + 1) ) - else: - if level.price in orderbook.levels[direction]: - del orderbook.levels[direction][level.price] - - # sort the level numerically - buys = sorted(orderbook.levels["buys"].values(), key=lambda x: x.price, reverse=True) - sells = sorted(orderbook.levels["sells"].values(), key=lambda x: x.price, reverse=True) - - # lowest sell price should be higher than the highest buy price - if len(buys) > 0 and len(sells) > 0: - highest_buy = buys[0].price - lowest_sell = sells[-1].price - print("Max buy: {} - Min sell: {}".format(highest_buy, lowest_sell)) - if highest_buy >= lowest_sell: - raise Exception("crossed orderbook, must restart") - - # for the example, print the list of buys and sells orders. - print("sells") - for k in sells: - print(k) - print("=========") - print("buys") - for k in buys: - print(k) - print("====================================") + ) + + print("updating orderbook with updates at sequence {}".format(update["sequence"])) + + # update orderbook + orderbook.sequence = int(update["sequence"]) + for direction, levels in {"buys": update["buys"], "sells": update["sells"]}.items(): + for level in levels: + if level["isActive"]: + # upsert level + orderbook.levels[direction][level["price"]] = PriceLevel( + price=Decimal(level["price"]), quantity=Decimal(level["quantity"]), timestamp=level["timestamp"] + ) + else: + if level["price"] in orderbook.levels[direction]: + del orderbook.levels[direction][level["price"]] + + # sort the level numerically + buys = sorted(orderbook.levels["buys"].values(), key=lambda x: x.price, reverse=True) + sells = sorted(orderbook.levels["sells"].values(), key=lambda x: x.price, reverse=True) + + # lowest sell price should be higher than the highest buy price + if len(buys) > 0 and len(sells) > 0: + highest_buy = buys[0].price + lowest_sell = sells[-1].price + print("Max buy: {} - Min sell: {}".format(highest_buy, lowest_sell)) + if highest_buy >= lowest_sell: + raise Exception("crossed orderbook, must restart") + + # for the example, print the list of buys and sells orders. + print("sells") + for k in sells: + print(k) + print("=========") + print("buys") + for k in buys: + print(k) + print("====================================") if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/9_StreamPositions.py b/examples/exchange_client/derivative_exchange_rpc/9_StreamPositions.py index cc808a7e..a035008e 100644 --- a/examples/exchange_client/derivative_exchange_rpc/9_StreamPositions.py +++ b/examples/exchange_client/derivative_exchange_rpc/9_StreamPositions.py @@ -1,18 +1,43 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def positions_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to derivative positions updates ({exception})") + + +def stream_closed_processor(): + print("The derivative positions updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - subaccount_id = "0xea98e3aa091a6676194df40ac089e40ab4604bf9000000000000000000000000" - positions = await client.stream_derivative_positions(market_id=market_id, subaccount_id=subaccount_id) - async for position in positions: - print(position) + market_ids = ["0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"] + subaccount_ids = ["0xea98e3aa091a6676194df40ac089e40ab4604bf9000000000000000000000000"] + + task = asyncio.get_event_loop().create_task( + client.listen_derivative_positions_updates( + callback=positions_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index cef3c829..bb88eae2 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -21,6 +21,7 @@ from pyinjective.client.indexer.grpc.indexer_grpc_spot_api import IndexerGrpcSpotApi from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_auction_stream import IndexerGrpcAuctionStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_derivative_stream import IndexerGrpcDerivativeStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_oracle_stream import IndexerGrpcOracleStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_spot_stream import IndexerGrpcSpotStream @@ -231,6 +232,12 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_derivative_stream_api = IndexerGrpcDerivativeStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) self.exchange_meta_stream_api = IndexerGrpcMetaStream( channel=self.exchange_channel, metadata_provider=lambda: self.network.exchange_metadata( @@ -1317,6 +1324,15 @@ async def listen_spot_orders_history_updates( ) async def stream_historical_derivative_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. + Please use `listen_derivative_orders_history_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_orders_history_updates instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.StreamOrdersHistoryRequest( market_id=market_id, direction=kwargs.get("direction"), @@ -1330,6 +1346,30 @@ async def stream_historical_derivative_orders(self, market_id: str, **kwargs): ) return self.stubDerivativeExchange.StreamOrdersHistory(request=req, metadata=metadata) + async def listen_derivative_orders_history_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + market_id: Optional[str] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + ): + await self.exchange_derivative_stream_api.stream_orders_history( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + subaccount_id=subaccount_id, + market_id=market_id, + order_types=order_types, + direction=direction, + state=state, + execution_types=execution_types, + ) + async def stream_spot_trades(self, **kwargs): """ This method is deprecated and will be removed soon. Please use `listen_spot_trades_updates` instead @@ -1479,12 +1519,32 @@ async def fetch_derivative_markets( ) async def stream_derivative_markets(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_market_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_market_updates instead", DeprecationWarning, stacklevel=2 + ) req = derivative_exchange_rpc_pb.StreamMarketRequest(market_ids=kwargs.get("market_ids")) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubDerivativeExchange.StreamMarket(request=req, metadata=metadata) + async def listen_derivative_market_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + ): + await self.exchange_derivative_stream_api.stream_markets( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + ) + async def get_derivative_orderbook(self, market_id: str): """ This method is deprecated and will be removed soon. Please use `fetch_derivative_orderbook_v2` instead @@ -1662,34 +1722,84 @@ async def fetch_derivative_trades( ) async def stream_derivative_orderbook_snapshot(self, market_ids: List[str]): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_orderbook_snapshots` instead + """ + warn( + "This method is deprecated. Use listen_derivative_orderbook_snapshots instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.StreamOrderbookV2Request(market_ids=market_ids) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubDerivativeExchange.StreamOrderbookV2(request=req, metadata=metadata) + async def listen_derivative_orderbook_snapshots( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_derivative_stream_api.stream_orderbook_v2( + market_ids=market_ids, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def stream_derivative_orderbook_update(self, market_ids: List[str]): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_orderbook_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_orderbook_updates instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.StreamOrderbookUpdateRequest(market_ids=market_ids) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubDerivativeExchange.StreamOrderbookUpdate(request=req, metadata=metadata) + async def listen_derivative_orderbook_updates( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_derivative_stream_api.stream_orderbook_update( + market_ids=market_ids, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def stream_derivative_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_orders_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_orders_updates instead", DeprecationWarning, stacklevel=2 + ) req = derivative_exchange_rpc_pb.StreamOrdersRequest( market_id=market_id, - execution_side=kwargs.get("execution_side"), - direction=kwargs.get("direction"), + order_side=kwargs.get("order_side"), subaccount_id=kwargs.get("subaccount_id"), skip=kwargs.get("skip"), limit=kwargs.get("limit"), start_time=kwargs.get("start_time"), end_time=kwargs.get("end_time"), market_ids=kwargs.get("market_ids"), - subaccount_ids=kwargs.get("subaccount_ids"), - execution_types=kwargs.get("execution_types"), + is_conditional=kwargs.get("is_conditional"), + order_type=kwargs.get("order_type"), + include_inactive=kwargs.get("include_inactive"), + subaccount_total_orders=kwargs.get("subaccount_total_orders"), trade_id=kwargs.get("trade_id"), - account_address=kwargs.get("account_address"), cid=kwargs.get("cid"), ) metadata = await self.network.exchange_metadata( @@ -1697,7 +1807,45 @@ async def stream_derivative_orders(self, market_id: str, **kwargs): ) return self.stubDerivativeExchange.StreamOrders(request=req, metadata=metadata) + async def listen_derivative_orders_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[PaginationOption] = None, + is_conditional: Optional[str] = None, + order_type: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + await self.exchange_derivative_stream_api.stream_orders( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + is_conditional=is_conditional, + order_type=order_type, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + pagination=pagination, + ) + async def stream_derivative_trades(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_trades_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_trades_updates instead", DeprecationWarning, stacklevel=2 + ) req = derivative_exchange_rpc_pb.StreamTradesRequest( market_id=kwargs.get("market_id"), execution_side=kwargs.get("execution_side"), @@ -1719,6 +1867,36 @@ async def stream_derivative_trades(self, **kwargs): ) return self.stubDerivativeExchange.StreamTrades(request=req, metadata=metadata) + async def listen_derivative_trades_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + subaccount_ids: Optional[List[str]] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + return await self.exchange_derivative_stream_api.stream_trades( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + pagination=pagination, + ) + async def get_derivative_positions(self, **kwargs): """ This method is deprecated and will be removed soon. Please use `fetch_derivative_positions` instead @@ -1752,6 +1930,14 @@ async def fetch_derivative_positions( ) async def stream_derivative_positions(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_positions_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_positions_updates instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.StreamPositionsRequest( market_id=kwargs.get("market_id"), market_ids=kwargs.get("market_ids"), @@ -1763,6 +1949,22 @@ async def stream_derivative_positions(self, **kwargs): ) return self.stubDerivativeExchange.StreamPositions(request=req, metadata=metadata) + async def listen_derivative_positions_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + ): + await self.exchange_derivative_stream_api.stream_positions( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + ) + async def get_derivative_liquidable_positions(self, **kwargs): """ This method is deprecated and will be removed soon. Please use `fetch_derivative_liquidable_positions` instead @@ -1835,7 +2037,7 @@ async def get_derivative_subaccount_trades(self, subaccount_id: str, **kwargs): ) return await self.stubDerivativeExchange.SubaccountTradesList(req) - async def fetch_subaccount_trades_list( + async def fetch_derivative_subaccount_trades_list( self, subaccount_id: str, market_id: Optional[str] = None, diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_derivative_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_derivative_stream.py new file mode 100644 index 00000000..a14a6e30 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_derivative_stream.py @@ -0,0 +1,196 @@ +from typing import Callable, List, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.exchange import ( + injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb, + injective_derivative_exchange_rpc_pb2_grpc as exchange_derivative_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcDerivativeStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_derivative_grpc.InjectiveDerivativeExchangeRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_market( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + ): + request = exchange_derivative_pb.StreamMarketRequest( + market_ids=market_ids, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamMarket, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orderbook_v2( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_derivative_pb.StreamOrderbookV2Request(market_ids=market_ids) + + await self._assistant.listen_stream( + call=self._stub.StreamOrderbookV2, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orderbook_update( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_derivative_pb.StreamOrderbookUpdateRequest(market_ids=market_ids) + + await self._assistant.listen_stream( + call=self._stub.StreamOrderbookUpdate, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_positions( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + ): + request = exchange_derivative_pb.StreamPositionsRequest(market_ids=market_ids, subaccount_ids=subaccount_ids) + + await self._assistant.listen_stream( + call=self._stub.StreamPositions, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orders( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[PaginationOption] = None, + is_conditional: Optional[str] = None, + order_type: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.StreamOrdersRequest( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + is_conditional=is_conditional, + order_type=order_type, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamOrders, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_trades( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + subaccount_ids: Optional[List[str]] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.StreamTradesRequest( + execution_side=execution_side, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamTrades, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orders_history( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + market_id: Optional[str] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + ): + request = exchange_derivative_pb.StreamOrdersHistoryRequest( + subaccount_id=subaccount_id, + market_id=market_id, + order_types=order_types, + direction=direction, + state=state, + execution_types=execution_types, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamOrdersHistory, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/tests/client/indexer/configurable_derivative_query_servicer.py b/tests/client/indexer/configurable_derivative_query_servicer.py index 083e431e..dfb44179 100644 --- a/tests/client/indexer/configurable_derivative_query_servicer.py +++ b/tests/client/indexer/configurable_derivative_query_servicer.py @@ -25,6 +25,14 @@ def __init__(self): self.subaccount_trades_list_responses = deque() self.orders_history_responses = deque() + self.stream_market_responses = deque() + self.stream_orderbook_v2_responses = deque() + self.stream_orderbook_update_responses = deque() + self.stream_positions_responses = deque() + self.stream_orders_responses = deque() + self.stream_trades_responses = deque() + self.stream_orders_history_responses = deque() + async def Markets(self, request: exchange_derivative_pb.MarketsRequest, context=None, metadata=None): return self.markets_responses.pop() @@ -81,3 +89,39 @@ async def SubaccountTradesList( async def OrdersHistory(self, request: exchange_derivative_pb.OrdersHistoryRequest, context=None, metadata=None): return self.orders_history_responses.pop() + + async def StreamMarket(self, request: exchange_derivative_pb.StreamMarketRequest, context=None, metadata=None): + for event in self.stream_market_responses: + yield event + + async def StreamOrderbookV2( + self, request: exchange_derivative_pb.StreamOrderbookV2Request, context=None, metadata=None + ): + for event in self.stream_orderbook_v2_responses: + yield event + + async def StreamOrderbookUpdate( + self, request: exchange_derivative_pb.StreamOrderbookUpdateRequest, context=None, metadata=None + ): + for event in self.stream_orderbook_update_responses: + yield event + + async def StreamPositions( + self, request: exchange_derivative_pb.StreamPositionsRequest, context=None, metadata=None + ): + for event in self.stream_positions_responses: + yield event + + async def StreamOrders(self, request: exchange_derivative_pb.StreamOrdersRequest, context=None, metadata=None): + for event in self.stream_orders_responses: + yield event + + async def StreamTrades(self, request: exchange_derivative_pb.StreamTradesRequest, context=None, metadata=None): + for event in self.stream_trades_responses: + yield event + + async def StreamOrdersHistory( + self, request: exchange_derivative_pb.StreamOrdersHistoryRequest, context=None, metadata=None + ): + for event in self.stream_orders_history_responses: + yield event diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_derivative_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_derivative_stream.py new file mode 100644 index 00000000..5dc2f79b --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_derivative_stream.py @@ -0,0 +1,709 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_derivative_stream import IndexerGrpcDerivativeStream +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb +from tests.client.indexer.configurable_derivative_query_servicer import ConfigurableDerivativeQueryServicer + + +@pytest.fixture +def derivative_servicer(): + return ConfigurableDerivativeQueryServicer() + + +class TestIndexerGrpcDerivativeStream: + @pytest.mark.asyncio + async def test_stream_market( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + quote_token_meta = exchange_derivative_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + perpetual_market_info = exchange_derivative_pb.PerpetualMarketInfo( + hourly_funding_rate_cap="0.000625", + hourly_interest_rate="0.00000416666", + next_funding_timestamp=1700064000, + funding_interval=3600, + ) + perpetual_market_funding = exchange_derivative_pb.PerpetualMarketFunding( + cumulative_funding="-82680.076492986572881307", + cumulative_price="-78.41752505919454668", + last_timestamp=1700004260, + ) + + market = exchange_derivative_pb.DerivativeMarketInfo( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + market_status="active", + ticker="INJ/USDT PERP", + oracle_base="0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3", + oracle_quote="0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588", + oracle_type="pyth", + oracle_scale_factor=6, + initial_margin_ratio="0.05", + maintenance_margin_ratio="0.02", + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + is_perpetual=True, + min_price_tick_size="100", + min_quantity_tick_size="0.0001", + perpetual_market_info=perpetual_market_info, + perpetual_market_funding=perpetual_market_funding, + ) + + derivative_servicer.stream_market_responses.append( + exchange_derivative_pb.StreamMarketResponse( + market=market, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + market_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: market_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_market( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[market.market_id], + ) + ) + expected_update = { + "market": { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "oracleBase": market.oracle_base, + "oracleQuote": market.oracle_quote, + "oracleType": market.oracle_type, + "oracleScaleFactor": market.oracle_scale_factor, + "initialMarginRatio": market.initial_margin_ratio, + "maintenanceMarginRatio": market.maintenance_margin_ratio, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "makerFeeRate": market.maker_fee_rate, + "takerFeeRate": market.taker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "isPerpetual": market.is_perpetual, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + "perpetualMarketInfo": { + "hourlyFundingRateCap": perpetual_market_info.hourly_funding_rate_cap, + "hourlyInterestRate": str(perpetual_market_info.hourly_interest_rate), + "nextFundingTimestamp": str(perpetual_market_info.next_funding_timestamp), + "fundingInterval": str(perpetual_market_info.funding_interval), + }, + "perpetualMarketFunding": { + "cumulativeFunding": perpetual_market_funding.cumulative_funding, + "cumulativePrice": perpetual_market_funding.cumulative_price, + "lastTimestamp": str(perpetual_market_funding.last_timestamp), + }, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(market_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orderbook_v2( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" + + buy = exchange_derivative_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_derivative_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_derivative_pb.DerivativeLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + derivative_servicer.stream_orderbook_v2_responses.append( + exchange_derivative_pb.StreamOrderbookV2Response( + orderbook=orderbook, + operation_type=operation_type, + timestamp=timestamp, + market_id=market_id, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + orderbook_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orderbook_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orderbook_v2( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[market_id], + ) + ) + expected_update = { + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + }, + "operationType": operation_type, + "timestamp": str(timestamp), + "marketId": market_id, + } + + first_update = await asyncio.wait_for(orderbook_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orderbook_update( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + buy = exchange_derivative_pb.PriceLevelUpdate( + price="0.000000000014198", + quantity="142000000000000000000", + is_active=True, + timestamp=1698982052141, + ) + sell = exchange_derivative_pb.PriceLevelUpdate( + price="0.00000000095699", + quantity="189000000000000000", + is_active=True, + timestamp=1698920369246, + ) + + level_updates = exchange_derivative_pb.OrderbookLevelUpdates( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + sequence=5506752, + buys=[buy], + sells=[sell], + updated_at=1698982083606, + ) + + derivative_servicer.stream_orderbook_update_responses.append( + exchange_derivative_pb.StreamOrderbookUpdateResponse( + orderbook_level_updates=level_updates, + operation_type=operation_type, + timestamp=timestamp, + market_id=level_updates.market_id, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + orderbook_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orderbook_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orderbook_update( + market_ids=[level_updates.market_id], + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "orderbookLevelUpdates": { + "marketId": level_updates.market_id, + "sequence": str(level_updates.sequence), + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "isActive": buy.is_active, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "isActive": sell.is_active, + "timestamp": str(sell.timestamp), + } + ], + "updatedAt": str(level_updates.updated_at), + }, + "operationType": operation_type, + "timestamp": str(timestamp), + "marketId": level_updates.market_id, + } + + first_update = await asyncio.wait_for(orderbook_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_positions( + self, + derivative_servicer, + ): + timestamp = 1672218001897 + + position = exchange_derivative_pb.DerivativePosition( + ticker="INJ/USDT PERP", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x1383dabde57e5aed55960ee43e158ae7118057d3000000000000000000000000", + direction="short", + quantity="0.070294765766186502", + entry_price="15980281.340438795311756847", + margin="561065.540974", + liquidation_price="23492052.224777", + mark_price="16197000", + aggregate_reduce_only_quantity="0", + updated_at=1700161202147, + created_at=-62135596800000, + ) + + derivative_servicer.stream_positions_responses.append( + exchange_derivative_pb.StreamPositionsResponse( + position=position, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + positions = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: positions.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_positions( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[position.market_id], + subaccount_ids=[position.subaccount_id], + ) + ) + expected_update = { + "position": { + "ticker": position.ticker, + "marketId": position.market_id, + "subaccountId": position.subaccount_id, + "direction": position.direction, + "quantity": position.quantity, + "entryPrice": position.entry_price, + "margin": position.margin, + "liquidationPrice": position.liquidation_price, + "markPrice": position.mark_price, + "aggregateReduceOnlyQuantity": position.aggregate_reduce_only_quantity, + "createdAt": str(position.created_at), + "updatedAt": str(position.updated_at), + }, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(positions.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orders( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + order = exchange_derivative_pb.DerivativeLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + is_reduce_only=False, + margin="2280000000", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + order_number=0, + order_type="", + is_conditional=False, + trigger_at=0, + placed_order_hash="", + execution_type="", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + derivative_servicer.stream_orders_responses.append( + exchange_derivative_pb.StreamOrdersResponse( + order=order, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + orders_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orders_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orders( + market_ids=[order.market_id], + order_side=order.order_side, + subaccount_id=order.subaccount_id, + is_conditional="true", + order_type="", + include_inactive=True, + subaccount_total_orders=True, + trade_id="7959737_3_0", + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "order": { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "isReduceOnly": order.is_reduce_only, + "margin": order.margin, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "orderNumber": str(order.order_number), + "orderType": order.order_type, + "isConditional": order.is_conditional, + "triggerAt": str(order.trigger_at), + "placedOrderHash": order.placed_order_hash, + "executionType": order.execution_type, + "txHash": order.tx_hash, + "cid": order.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(orders_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_trades( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + position_delta = exchange_derivative_pb.PositionDelta( + trade_direction="buy", + execution_price="13945600", + execution_quantity="5", + execution_margin="69728000", + ) + + trade = exchange_derivative_pb.DerivativeTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + trade_execution_type="limitMatchNewOrder", + is_liquidation=False, + position_delta=position_delta, + payout="0", + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + derivative_servicer.stream_trades_responses.append( + exchange_derivative_pb.StreamTradesResponse( + trade=trade, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + trade_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: trade_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_trades( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[trade.market_id], + subaccount_ids=[trade.subaccount_id], + execution_side=trade.execution_side, + direction=position_delta.trade_direction, + execution_types=[trade.trade_execution_type], + trade_id="7959737_3_0", + account_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + cid=trade.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + ) + expected_update = { + "trade": { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "isLiquidation": trade.is_liquidation, + "positionDelta": { + "tradeDirection": position_delta.trade_direction, + "executionPrice": position_delta.execution_price, + "executionQuantity": position_delta.execution_quantity, + "executionMargin": position_delta.execution_margin, + }, + "payout": trade.payout, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(trade_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orders_history( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + order = exchange_derivative_pb.DerivativeOrderHistory( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + is_active=True, + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + execution_type="limit", + order_type="buy_po", + price="0.000000000017541", + trigger_price="0", + quantity="50955000000000000000", + filled_quantity="1000000000000000", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + is_reduce_only=False, + direction="buy", + is_conditional=False, + trigger_at=0, + placed_order_hash="", + margin="2280000000", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + derivative_servicer.stream_orders_history_responses.append( + exchange_derivative_pb.StreamOrdersHistoryResponse( + order=order, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + orders_history_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orders_history_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orders_history( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + subaccount_id=order.subaccount_id, + market_id=order.market_id, + order_types=[order.order_type], + direction=order.direction, + state=order.state, + execution_types=[order.execution_type], + ) + ) + expected_update = { + "order": { + "orderHash": order.order_hash, + "marketId": order.market_id, + "isActive": order.is_active, + "subaccountId": order.subaccount_id, + "executionType": order.execution_type, + "orderType": order.order_type, + "price": order.price, + "triggerPrice": order.trigger_price, + "quantity": order.quantity, + "filledQuantity": order.filled_quantity, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "isReduceOnly": order.is_reduce_only, + "direction": order.direction, + "isConditional": order.is_conditional, + "triggerAt": str(order.trigger_at), + "placedOrderHash": order.placed_order_hash, + "margin": order.margin, + "txHash": order.tx_hash, + "cid": order.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(orders_history_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 0d6e7bc0..40e23967 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -888,7 +888,7 @@ async def test_stream_spot_orderbook_update_deprecation_warning( network=Network.local(), ) client.stubSpotExchange = spot_servicer - spot_servicer.stream_orderbook_v2_responses.append(exchange_spot_pb.StreamOrderbookUpdateRequest()) + spot_servicer.stream_orderbook_update_responses.append(exchange_spot_pb.StreamOrderbookUpdateRequest()) with catch_warnings(record=True) as all_warnings: await client.stream_spot_orderbook_update(market_ids=[]) @@ -1235,3 +1235,141 @@ async def test_get_historical_derivative_orders_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orders_history instead" + + @pytest.mark.asyncio + async def test_stream_derivative_markets_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.stream_market_responses.append(exchange_derivative_pb.StreamMarketResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_markets() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_market_updates instead" + + @pytest.mark.asyncio + async def test_stream_derivative_orderbook_snapshot_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + derivative_servicer.stream_orderbook_v2_responses.append(exchange_derivative_pb.StreamOrderbookV2Response()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_orderbook_snapshot(market_ids=[]) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) + == "This method is deprecated. Use listen_derivative_orderbook_snapshots instead" + ) + + @pytest.mark.asyncio + async def test_stream_derivative_orderbook_update_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.stream_orderbook_update_responses.append( + exchange_derivative_pb.StreamOrderbookUpdateRequest() + ) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_orderbook_update(market_ids=[]) + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_orderbook_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_derivative_positions_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.stream_positions_responses.append(exchange_derivative_pb.StreamPositionsRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_positions() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_positions_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_derivative_orders_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = derivative_servicer + derivative_servicer.stream_orders_responses.append(exchange_derivative_pb.StreamOrdersRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_orders(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_orders_updates instead" + + @pytest.mark.asyncio + async def test_stream_derivative_trades_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = derivative_servicer + derivative_servicer.stream_orders_responses.append(exchange_derivative_pb.StreamTradesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_trades() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_trades_updates instead" + + @pytest.mark.asyncio + async def test_stream_historical_derivative_orders_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = derivative_servicer + derivative_servicer.stream_orders_history_responses.append(exchange_spot_pb.StreamOrdersHistoryRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_historical_derivative_orders(market_id="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert ( + str(all_warnings[0].message) + == "This method is deprecated. Use listen_derivative_orders_history_updates instead" + ) From cfb507e0444073891b3bf44d7a4c240700c6de74 Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 29 Nov 2023 10:35:13 -0300 Subject: [PATCH 21/27] (feat) Added low level API components for the exchange portfolio module, with unit tests. Included new functions in AsyncClient to use the low level API components and marked the old functions as deprecated. Updated the examples to use the new AsyncClient functions. --- .../portfolio_rpc/1_AccountPortfolio.py | 2 +- .../portfolio_rpc/2_StreamAccountPortfolio.py | 31 ++++- pyinjective/async_client.py | 45 +++++++ .../grpc/indexer_grpc_portfolio_api.py | 24 ++++ .../indexer_grpc_portfolio_stream.py | 38 ++++++ .../configurable_portfolio_query_servicer.py | 24 ++++ .../grpc/test_indexer_grpc_portfolio_api.py | 117 ++++++++++++++++++ .../test_indexer_grpc_portfolio_stream.py | 76 ++++++++++++ .../test_async_client_deprecation_warnings.py | 45 +++++++ 9 files changed, 397 insertions(+), 5 deletions(-) create mode 100644 pyinjective/client/indexer/grpc/indexer_grpc_portfolio_api.py create mode 100644 pyinjective/client/indexer/grpc_stream/indexer_grpc_portfolio_stream.py create mode 100644 tests/client/indexer/configurable_portfolio_query_servicer.py create mode 100644 tests/client/indexer/grpc/test_indexer_grpc_portfolio_api.py create mode 100644 tests/client/indexer/stream_grpc/test_indexer_grpc_portfolio_stream.py diff --git a/examples/exchange_client/portfolio_rpc/1_AccountPortfolio.py b/examples/exchange_client/portfolio_rpc/1_AccountPortfolio.py index 965d40c7..232f8582 100644 --- a/examples/exchange_client/portfolio_rpc/1_AccountPortfolio.py +++ b/examples/exchange_client/portfolio_rpc/1_AccountPortfolio.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) account_address = "inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt" - portfolio = await client.get_account_portfolio(account_address=account_address) + portfolio = await client.fetch_account_portfolio(account_address=account_address) print(portfolio) diff --git a/examples/exchange_client/portfolio_rpc/2_StreamAccountPortfolio.py b/examples/exchange_client/portfolio_rpc/2_StreamAccountPortfolio.py index 53d7c4da..4b3b6a04 100644 --- a/examples/exchange_client/portfolio_rpc/2_StreamAccountPortfolio.py +++ b/examples/exchange_client/portfolio_rpc/2_StreamAccountPortfolio.py @@ -1,17 +1,40 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def account_portfolio_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to account portfolio updates ({exception})") + + +def stream_closed_processor(): + print("The account portfolio updates stream has been closed") + + async def main() -> None: network = Network.testnet() client = AsyncClient(network) account_address = "inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt" - updates = await client.stream_account_portfolio(account_address=account_address) - async for update in updates: - print("Account portfolio Update:\n") - print(update) + + task = asyncio.get_event_loop().create_task( + client.listen_account_portfolio_updates( + account_address=account_address, + callback=account_portfolio_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index bb88eae2..957b8f76 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -18,12 +18,14 @@ from pyinjective.client.indexer.grpc.indexer_grpc_insurance_api import IndexerGrpcInsuranceApi from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi from pyinjective.client.indexer.grpc.indexer_grpc_oracle_api import IndexerGrpcOracleApi +from pyinjective.client.indexer.grpc.indexer_grpc_portfolio_api import IndexerGrpcPortfolioApi from pyinjective.client.indexer.grpc.indexer_grpc_spot_api import IndexerGrpcSpotApi from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_auction_stream import IndexerGrpcAuctionStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_derivative_stream import IndexerGrpcDerivativeStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_oracle_stream import IndexerGrpcOracleStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_portfolio_stream import IndexerGrpcPortfolioStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_spot_stream import IndexerGrpcSpotStream from pyinjective.client.model.pagination import PaginationOption from pyinjective.composer import Composer @@ -213,6 +215,12 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_portfolio_api = IndexerGrpcPortfolioApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) self.exchange_spot_api = IndexerGrpcSpotApi( channel=self.exchange_channel, metadata_provider=lambda: self.network.exchange_metadata( @@ -250,6 +258,12 @@ def __init__( metadata_query_provider=self._exchange_cookie_metadata_requestor ), ) + self.exchange_portfolio_stream_api = IndexerGrpcPortfolioStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) self.exchange_spot_stream_api = IndexerGrpcSpotStream( channel=self.exchange_channel, metadata_provider=lambda: self.network.exchange_metadata( @@ -2137,10 +2151,23 @@ async def fetch_binary_options_market(self, market_id: str) -> Dict[str, Any]: # PortfolioRPC async def get_account_portfolio(self, account_address: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_account_portfolio` instead + """ + warn("This method is deprecated. Use fetch_account_portfolio instead", DeprecationWarning, stacklevel=2) req = portfolio_rpc_pb.AccountPortfolioRequest(account_address=account_address) return await self.stubPortfolio.AccountPortfolio(req) + async def fetch_account_portfolio(self, account_address: str) -> Dict[str, Any]: + return await self.exchange_portfolio_api.fetch_account_portfolio(account_address=account_address) + async def stream_account_portfolio(self, account_address: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_account_portfolio_updates` instead + """ + warn( + "This method is deprecated. Use listen_account_portfolio_updates instead", DeprecationWarning, stacklevel=2 + ) req = portfolio_rpc_pb.StreamAccountPortfolioRequest( account_address=account_address, subaccount_id=kwargs.get("subaccount_id"), type=kwargs.get("type") ) @@ -2149,6 +2176,24 @@ async def stream_account_portfolio(self, account_address: str, **kwargs): ) return self.stubPortfolio.StreamAccountPortfolio(request=req, metadata=metadata) + async def listen_account_portfolio_updates( + self, + account_address: str, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + update_type: Optional[str] = None, + ): + await self.exchange_portfolio_stream_api.stream_account_portfolio( + account_address=account_address, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + subaccount_id=subaccount_id, + update_type=update_type, + ) + async def chain_stream( self, bank_balances_filter: Optional[chain_stream_query.BankBalancesFilter] = None, diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_portfolio_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_portfolio_api.py new file mode 100644 index 00000000..a8fd319c --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_portfolio_api.py @@ -0,0 +1,24 @@ +from typing import Any, Callable, Dict + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_portfolio_rpc_pb2 as exchange_portfolio_pb, + injective_portfolio_rpc_pb2_grpc as exchange_portfolio_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcPortfolioApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_portfolio_grpc.InjectivePortfolioRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_account_portfolio(self, account_address: str) -> Dict[str, Any]: + request = exchange_portfolio_pb.AccountPortfolioRequest(account_address=account_address) + response = await self._execute_call(call=self._stub.AccountPortfolio, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_portfolio_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_portfolio_stream.py new file mode 100644 index 00000000..59b0acfa --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_portfolio_stream.py @@ -0,0 +1,38 @@ +from typing import Callable, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_portfolio_rpc_pb2 as exchange_portfolio_pb, + injective_portfolio_rpc_pb2_grpc as exchange_portfolio_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcPortfolioStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_portfolio_grpc.InjectivePortfolioRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_account_portfolio( + self, + account_address: str, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + update_type: Optional[str] = None, + ): + request = exchange_portfolio_pb.StreamAccountPortfolioRequest( + account_address=account_address, + subaccount_id=subaccount_id, + type=update_type, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamAccountPortfolio, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/tests/client/indexer/configurable_portfolio_query_servicer.py b/tests/client/indexer/configurable_portfolio_query_servicer.py new file mode 100644 index 00000000..50d329ca --- /dev/null +++ b/tests/client/indexer/configurable_portfolio_query_servicer.py @@ -0,0 +1,24 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_portfolio_rpc_pb2 as exchange_portfolio_pb, + injective_portfolio_rpc_pb2_grpc as exchange_portfolio_grpc, +) + + +class ConfigurablePortfolioQueryServicer(exchange_portfolio_grpc.InjectivePortfolioRPCServicer): + def __init__(self): + super().__init__() + self.account_portfolio_responses = deque() + self.stream_account_portfolio_responses = deque() + + async def AccountPortfolio( + self, request: exchange_portfolio_pb.AccountPortfolioRequest, context=None, metadata=None + ): + return self.account_portfolio_responses.pop() + + async def StreamAccountPortfolio( + self, request: exchange_portfolio_pb.StreamAccountPortfolioRequest, context=None, metadata=None + ): + for event in self.stream_account_portfolio_responses: + yield event diff --git a/tests/client/indexer/grpc/test_indexer_grpc_portfolio_api.py b/tests/client/indexer/grpc/test_indexer_grpc_portfolio_api.py new file mode 100644 index 00000000..86a50382 --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_portfolio_api.py @@ -0,0 +1,117 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_portfolio_api import IndexerGrpcPortfolioApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_portfolio_rpc_pb2 as exchange_portfolio_pb +from tests.client.indexer.configurable_portfolio_query_servicer import ConfigurablePortfolioQueryServicer + + +@pytest.fixture +def portfolio_servicer(): + return ConfigurablePortfolioQueryServicer() + + +class TestIndexerGrpcPortfolioApi: + @pytest.mark.asyncio + async def test_fetch_account_portfolio( + self, + portfolio_servicer, + ): + coin = exchange_portfolio_pb.Coin( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + amount="2322098", + ) + subaccount_deposit = exchange_portfolio_pb.SubaccountDeposit( + total_balance="0.170858923182467801", + available_balance="0.170858923182467801", + ) + subaccount_balance = exchange_portfolio_pb.SubaccountBalanceV2( + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000000", + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + deposit=subaccount_deposit, + ) + position = exchange_portfolio_pb.DerivativePosition( + ticker="INJ/USDT PERP", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x1383dabde57e5aed55960ee43e158ae7118057d3000000000000000000000000", + direction="short", + quantity="0.070294765766186502", + entry_price="15980281.340438795311756847", + margin="561065.540974", + liquidation_price="23492052.224777", + mark_price="16197000", + aggregate_reduce_only_quantity="0", + updated_at=1700161202147, + created_at=-62135596800000, + ) + positions_with_upnl = exchange_portfolio_pb.PositionsWithUPNL( + position=position, + unrealized_pnl="-364.479654577777780880", + ) + + portfolio = exchange_portfolio_pb.Portfolio( + account_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + bank_balances=[coin], + subaccounts=[subaccount_balance], + positions_with_upnl=[positions_with_upnl], + ) + + portfolio_servicer.account_portfolio_responses.append( + exchange_portfolio_pb.AccountPortfolioResponse( + portfolio=portfolio, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcPortfolioApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = portfolio_servicer + + result_auction = await api.fetch_account_portfolio(account_address=portfolio.account_address) + expected_auction = { + "portfolio": { + "accountAddress": portfolio.account_address, + "bankBalances": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "subaccounts": [ + { + "subaccountId": subaccount_balance.subaccount_id, + "denom": subaccount_balance.denom, + "deposit": { + "totalBalance": subaccount_deposit.total_balance, + "availableBalance": subaccount_deposit.available_balance, + }, + } + ], + "positionsWithUpnl": [ + { + "position": { + "ticker": position.ticker, + "marketId": position.market_id, + "subaccountId": position.subaccount_id, + "direction": position.direction, + "quantity": position.quantity, + "entryPrice": position.entry_price, + "margin": position.margin, + "liquidationPrice": position.liquidation_price, + "markPrice": position.mark_price, + "aggregateReduceOnlyQuantity": position.aggregate_reduce_only_quantity, + "createdAt": str(position.created_at), + "updatedAt": str(position.updated_at), + }, + "unrealizedPnl": positions_with_upnl.unrealized_pnl, + }, + ], + } + } + + assert result_auction == expected_auction + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_portfolio_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_portfolio_stream.py new file mode 100644 index 00000000..2f09ed40 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_portfolio_stream.py @@ -0,0 +1,76 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_portfolio_stream import IndexerGrpcPortfolioStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_portfolio_rpc_pb2 as exchange_portfolio_pb +from tests.client.indexer.configurable_portfolio_query_servicer import ConfigurablePortfolioQueryServicer + + +@pytest.fixture +def portfolio_servicer(): + return ConfigurablePortfolioQueryServicer() + + +class TestIndexerGrpcPortfolioStream: + @pytest.mark.asyncio + async def test_stream_account_portfolio( + self, + portfolio_servicer, + ): + update_type = "total_balance" + denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" + amount = "1000000000000000000" + subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000000" + timestamp = 1675426622603 + + portfolio_servicer.stream_account_portfolio_responses.append( + exchange_portfolio_pb.StreamAccountPortfolioResponse( + type=update_type, + denom=denom, + amount=amount, + subaccount_id=subaccount_id, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcPortfolioStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = portfolio_servicer + + portfolio_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: portfolio_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_account_portfolio( + account_address="test_address", + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + subaccount_id=subaccount_id, + update_type=update_type, + ) + ) + expected_update = { + "type": update_type, + "denom": denom, + "amount": amount, + "subaccountId": subaccount_id, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(portfolio_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 40e23967..f20f720c 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -14,6 +14,7 @@ injective_insurance_rpc_pb2 as exchange_insurance_pb, injective_meta_rpc_pb2 as exchange_meta_pb, injective_oracle_rpc_pb2 as exchange_oracle_pb, + injective_portfolio_rpc_pb2 as exchange_portfolio_pb, injective_spot_exchange_rpc_pb2 as exchange_spot_pb, ) from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb @@ -26,6 +27,7 @@ from tests.client.indexer.configurable_insurance_query_servicer import ConfigurableInsuranceQueryServicer from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer +from tests.client.indexer.configurable_portfolio_query_servicer import ConfigurablePortfolioQueryServicer from tests.client.indexer.configurable_spot_query_servicer import ConfigurableSpotQueryServicer from tests.core.tx.grpc.configurable_tx_query_servicer import ConfigurableTxQueryServicer @@ -75,6 +77,11 @@ def oracle_servicer(): return ConfigurableOracleQueryServicer() +@pytest.fixture +def portfolio_servicer(): + return ConfigurablePortfolioQueryServicer() + + @pytest.fixture def spot_servicer(): return ConfigurableSpotQueryServicer() @@ -1373,3 +1380,41 @@ async def test_stream_historical_derivative_orders_deprecation_warning( str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_orders_history_updates instead" ) + + @pytest.mark.asyncio + async def test_get_account_portfolio_deprecation_warning( + self, + portfolio_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubPortfolio = portfolio_servicer + portfolio_servicer.account_portfolio_responses.append(exchange_portfolio_pb.AccountPortfolioResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_account_portfolio(account_address="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_account_portfolio instead" + + @pytest.mark.asyncio + async def test_stream_account_portfolio_deprecation_warning( + self, + portfolio_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubPortfolio = portfolio_servicer + portfolio_servicer.stream_account_portfolio_responses.append( + exchange_portfolio_pb.StreamAccountPortfolioResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.stream_account_portfolio(account_address="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_account_portfolio_updates instead" From 25adca0a6c51d32f32711c283a9b72ff32d85274 Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 1 Dec 2023 12:30:17 -0300 Subject: [PATCH 22/27] (feat) Added low level API components for the exchange explorer module, with unit tests. Included new functions in AsyncClient to use the low level API components and marked the old functions as deprecated. Updated the examples to use the new AsyncClient functions. --- .../explorer_rpc/10_GetIBCTransfers.py | 9 +- .../explorer_rpc/1_GetTxByHash.py | 4 +- .../explorer_rpc/2_AccountTxs.py | 10 +- .../exchange_client/explorer_rpc/3_Blocks.py | 6 +- .../exchange_client/explorer_rpc/4_Block.py | 2 +- .../explorer_rpc/5_TxsRequest.py | 4 +- .../explorer_rpc/8_GetPeggyDeposits.py | 2 +- .../explorer_rpc/9_GetPeggyWithdrawals.py | 4 +- pyinjective/async_client.py | 149 ++ .../indexer/grpc/indexer_grpc_explorer_api.py | 329 ++++ pyinjective/composer.py | 111 +- .../configurable_explorer_query_servicer.py | 101 ++ .../grpc/test_indexer_grpc_explorer_api.py | 1582 +++++++++++++++++ .../test_async_client_deprecation_warnings.py | 151 ++ 14 files changed, 2390 insertions(+), 74 deletions(-) create mode 100644 pyinjective/client/indexer/grpc/indexer_grpc_explorer_api.py create mode 100644 tests/client/indexer/configurable_explorer_query_servicer.py create mode 100644 tests/client/indexer/grpc/test_indexer_grpc_explorer_api.py diff --git a/examples/exchange_client/explorer_rpc/10_GetIBCTransfers.py b/examples/exchange_client/explorer_rpc/10_GetIBCTransfers.py index 62ca0c24..b6219b1c 100644 --- a/examples/exchange_client/explorer_rpc/10_GetIBCTransfers.py +++ b/examples/exchange_client/explorer_rpc/10_GetIBCTransfers.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -16,15 +17,15 @@ async def main() -> None: dest_port = "transfer" limit = 1 skip = 10 - ibc_transfers = await client.get_ibc_transfers( + pagination = PaginationOption(limit=limit, skip=skip) + ibc_transfers = await client.fetch_ibc_transfer_txs( sender=sender, receiver=receiver, src_channel=src_channel, src_port=src_port, - destination_channel=destination_channel, + dest_channel=destination_channel, dest_port=dest_port, - limit=limit, - skip=skip, + pagination=pagination, ) print(ibc_transfers) diff --git a/examples/exchange_client/explorer_rpc/1_GetTxByHash.py b/examples/exchange_client/explorer_rpc/1_GetTxByHash.py index 33e6abe4..f1158993 100644 --- a/examples/exchange_client/explorer_rpc/1_GetTxByHash.py +++ b/examples/exchange_client/explorer_rpc/1_GetTxByHash.py @@ -11,10 +11,10 @@ async def main() -> None: client = AsyncClient(network) composer = Composer(network=network.string()) tx_hash = "0F3EBEC1882E1EEAC5B7BDD836E976250F1CD072B79485877CEACCB92ACDDF52" - transaction_response = await client.get_tx_by_hash(tx_hash=tx_hash) + transaction_response = await client.fetch_tx_by_tx_hash(tx_hash=tx_hash) print(transaction_response) - transaction_messages = composer.UnpackTransactionMessages(transaction=transaction_response.data) + transaction_messages = composer.unpack_transaction_messages(transaction_data=transaction_response["data"]) print(transaction_messages) first_message = transaction_messages[0] print(first_message) diff --git a/examples/exchange_client/explorer_rpc/2_AccountTxs.py b/examples/exchange_client/explorer_rpc/2_AccountTxs.py index b1e39445..f743e302 100644 --- a/examples/exchange_client/explorer_rpc/2_AccountTxs.py +++ b/examples/exchange_client/explorer_rpc/2_AccountTxs.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.composer import Composer from pyinjective.core.network import Network @@ -13,9 +14,14 @@ async def main() -> None: address = "inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex" message_type = "cosmos.bank.v1beta1.MsgSend" limit = 2 - transactions_response = await client.get_account_txs(address=address, type=message_type, limit=limit) + pagination = PaginationOption(limit=limit) + transactions_response = await client.fetch_account_txs( + address=address, + message_type=message_type, + pagination=pagination, + ) print(transactions_response) - first_transaction_messages = composer.UnpackTransactionMessages(transaction=transactions_response.data[0]) + first_transaction_messages = composer.unpack_transaction_messages(transaction_data=transactions_response["data"][0]) print(first_transaction_messages) first_message = first_transaction_messages[0] print(first_message) diff --git a/examples/exchange_client/explorer_rpc/3_Blocks.py b/examples/exchange_client/explorer_rpc/3_Blocks.py index 72739096..4807030c 100644 --- a/examples/exchange_client/explorer_rpc/3_Blocks.py +++ b/examples/exchange_client/explorer_rpc/3_Blocks.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -9,8 +10,9 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) limit = 2 - block = await client.get_blocks(limit=limit) - print(block) + pagination = PaginationOption(limit=limit) + blocks = await client.fetch_blocks(pagination=pagination) + print(blocks) if __name__ == "__main__": diff --git a/examples/exchange_client/explorer_rpc/4_Block.py b/examples/exchange_client/explorer_rpc/4_Block.py index e043bdcd..b41d5595 100644 --- a/examples/exchange_client/explorer_rpc/4_Block.py +++ b/examples/exchange_client/explorer_rpc/4_Block.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) block_height = "5825046" - block = await client.get_block(block_height=block_height) + block = await client.fetch_block(block_id=block_height) print(block) diff --git a/examples/exchange_client/explorer_rpc/5_TxsRequest.py b/examples/exchange_client/explorer_rpc/5_TxsRequest.py index 8282816b..70b91f68 100644 --- a/examples/exchange_client/explorer_rpc/5_TxsRequest.py +++ b/examples/exchange_client/explorer_rpc/5_TxsRequest.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -9,7 +10,8 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) limit = 2 - txs = await client.get_txs(limit=limit) + pagination = PaginationOption(limit=limit) + txs = await client.fetch_txs(pagination=pagination) print(txs) diff --git a/examples/exchange_client/explorer_rpc/8_GetPeggyDeposits.py b/examples/exchange_client/explorer_rpc/8_GetPeggyDeposits.py index 4fde668f..936ede47 100644 --- a/examples/exchange_client/explorer_rpc/8_GetPeggyDeposits.py +++ b/examples/exchange_client/explorer_rpc/8_GetPeggyDeposits.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) receiver = "inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex" - peggy_deposits = await client.get_peggy_deposits(receiver=receiver) + peggy_deposits = await client.fetch_peggy_deposit_txs(receiver=receiver) print(peggy_deposits) diff --git a/examples/exchange_client/explorer_rpc/9_GetPeggyWithdrawals.py b/examples/exchange_client/explorer_rpc/9_GetPeggyWithdrawals.py index def1fedf..0c38e9dc 100644 --- a/examples/exchange_client/explorer_rpc/9_GetPeggyWithdrawals.py +++ b/examples/exchange_client/explorer_rpc/9_GetPeggyWithdrawals.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -10,7 +11,8 @@ async def main() -> None: client = AsyncClient(network) sender = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" limit = 2 - peggy_deposits = await client.get_peggy_withdrawals(sender=sender, limit=limit) + pagination = PaginationOption(limit=limit) + peggy_deposits = await client.fetch_peggy_withdrawal_txs(sender=sender, pagination=pagination) print(peggy_deposits) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 957b8f76..d78bb862 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -15,6 +15,7 @@ from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi from pyinjective.client.indexer.grpc.indexer_grpc_auction_api import IndexerGrpcAuctionApi from pyinjective.client.indexer.grpc.indexer_grpc_derivative_api import IndexerGrpcDerivativeApi +from pyinjective.client.indexer.grpc.indexer_grpc_explorer_api import IndexerGrpcExplorerApi from pyinjective.client.indexer.grpc.indexer_grpc_insurance_api import IndexerGrpcInsuranceApi from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi from pyinjective.client.indexer.grpc.indexer_grpc_oracle_api import IndexerGrpcOracleApi @@ -271,6 +272,13 @@ def __init__( ), ) + self.exchange_explorer_api = IndexerGrpcExplorerApi( + channel=self.explorer_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._explorer_cookie_metadata_requestor + ), + ) + async def all_tokens(self) -> Dict[str, Token]: if self._tokens is None: async with self._tokens_and_markets_initialization_lock: @@ -601,10 +609,22 @@ async def listen_keepalive( # Explorer RPC async def get_tx_by_hash(self, tx_hash: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_tx_by_tx_hash` instead + """ + warn("This method is deprecated. Use fetch_tx_by_tx_hash instead", DeprecationWarning, stacklevel=2) + req = explorer_rpc_pb.GetTxByTxHashRequest(hash=tx_hash) return await self.stubExplorer.GetTxByTxHash(req) + async def fetch_tx_by_tx_hash(self, tx_hash: str) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_tx_by_tx_hash(tx_hash=tx_hash) + async def get_account_txs(self, address: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_account_txs` instead + """ + warn("This method is deprecated. Use fetch_account_txs instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetAccountTxsRequest( address=address, before=kwargs.get("before"), @@ -616,7 +636,35 @@ async def get_account_txs(self, address: str, **kwargs): ) return await self.stubExplorer.GetAccountTxs(req) + async def fetch_account_txs( + self, + address: str, + before: Optional[int] = None, + after: Optional[int] = None, + message_type: Optional[str] = None, + module: Optional[str] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + status: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_account_txs( + address=address, + before=before, + after=after, + message_type=message_type, + module=module, + from_number=from_number, + to_number=to_number, + status=status, + pagination=pagination, + ) + async def get_blocks(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_blocks` instead + """ + warn("This method is deprecated. Use fetch_blocks instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetBlocksRequest( before=kwargs.get("before"), after=kwargs.get("after"), @@ -624,11 +672,30 @@ async def get_blocks(self, **kwargs): ) return await self.stubExplorer.GetBlocks(req) + async def fetch_blocks( + self, + before: Optional[int] = None, + after: Optional[int] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_blocks(before=before, after=after, pagination=pagination) + async def get_block(self, block_height: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_block` instead + """ + warn("This method is deprecated. Use fetch_block instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetBlockRequest(id=block_height) return await self.stubExplorer.GetBlock(req) + async def fetch_block(self, block_id: str) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_block(block_id=block_id) + async def get_txs(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_txs` instead + """ + warn("This method is deprecated. Use fetch_txs instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetTxsRequest( before=kwargs.get("before"), after=kwargs.get("after"), @@ -639,6 +706,28 @@ async def get_txs(self, **kwargs): ) return await self.stubExplorer.GetTxs(req) + async def fetch_txs( + self, + before: Optional[int] = None, + after: Optional[int] = None, + message_type: Optional[str] = None, + module: Optional[str] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + status: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_txs( + before=before, + after=after, + message_type=message_type, + module=module, + from_number=from_number, + to_number=to_number, + status=status, + pagination=pagination, + ) + async def stream_txs(self): req = explorer_rpc_pb.StreamTxsRequest() return self.stubExplorer.StreamTxs(req) @@ -648,6 +737,10 @@ async def stream_blocks(self): return self.stubExplorer.StreamBlocks(req) async def get_peggy_deposits(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_peggy_deposit_txs` instead + """ + warn("This method is deprecated. Use fetch_peggy_deposit_txs instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetPeggyDepositTxsRequest( sender=kwargs.get("sender"), receiver=kwargs.get("receiver"), @@ -656,7 +749,23 @@ async def get_peggy_deposits(self, **kwargs): ) return await self.stubExplorer.GetPeggyDepositTxs(req) + async def fetch_peggy_deposit_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_peggy_deposit_txs( + sender=sender, + receiver=receiver, + pagination=pagination, + ) + async def get_peggy_withdrawals(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_peggy_withdrawal_txs` instead + """ + warn("This method is deprecated. Use fetch_peggy_withdrawal_txs instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetPeggyWithdrawalTxsRequest( sender=kwargs.get("sender"), receiver=kwargs.get("receiver"), @@ -665,7 +774,23 @@ async def get_peggy_withdrawals(self, **kwargs): ) return await self.stubExplorer.GetPeggyWithdrawalTxs(req) + async def fetch_peggy_withdrawal_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_peggy_withdrawal_txs( + sender=sender, + receiver=receiver, + pagination=pagination, + ) + async def get_ibc_transfers(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_ibc_transfer_txs` instead + """ + warn("This method is deprecated. Use fetch_ibc_transfer_txs instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetIBCTransferTxsRequest( sender=kwargs.get("sender"), receiver=kwargs.get("receiver"), @@ -678,6 +803,26 @@ async def get_ibc_transfers(self, **kwargs): ) return await self.stubExplorer.GetIBCTransferTxs(req) + async def fetch_ibc_transfer_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + src_channel: Optional[str] = None, + src_port: Optional[str] = None, + dest_channel: Optional[str] = None, + dest_port: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_ibc_transfer_txs( + sender=sender, + receiver=receiver, + src_channel=src_channel, + src_port=src_port, + dest_channel=dest_channel, + dest_port=dest_port, + pagination=pagination, + ) + # AccountsRPC async def stream_subaccount_balance(self, subaccount_id: str, **kwargs): @@ -2390,6 +2535,10 @@ def _exchange_cookie_metadata_requestor(self) -> Coroutine: request = exchange_meta_rpc_pb.VersionRequest() return self.stubMeta.Version(request).initial_metadata() + def _explorer_cookie_metadata_requestor(self) -> Coroutine: + request = explorer_rpc_pb.GetBlocksRequest() + return self.stubExplorer.GetBlocks(request).initial_metadata() + def _initialize_timeout_height_sync_task(self): self._cancel_timeout_height_sync_task() self._timeout_height_sync_task = asyncio.get_event_loop().create_task(self._timeout_height_sync_process()) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_explorer_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_explorer_api.py new file mode 100644 index 00000000..89bfc3c7 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_explorer_api.py @@ -0,0 +1,329 @@ +from typing import Any, Callable, Dict, List, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.exchange import ( + injective_explorer_rpc_pb2 as exchange_explorer_pb, + injective_explorer_rpc_pb2_grpc as exchange_explorer_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcExplorerApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_explorer_grpc.InjectiveExplorerRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_account_txs( + self, + address: str, + before: Optional[int] = None, + after: Optional[int] = None, + message_type: Optional[str] = None, + module: Optional[str] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + status: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetAccountTxsRequest( + address=address, + before=before, + after=after, + limit=pagination.limit, + skip=pagination.skip, + type=message_type, + module=module, + from_number=from_number, + to_number=to_number, + start_time=pagination.start_time, + end_time=pagination.end_time, + status=status, + ) + + response = await self._execute_call(call=self._stub.GetAccountTxs, request=request) + + return response + + async def fetch_contract_txs( + self, + address: str, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetAccountTxsRequest( + address=address, + limit=pagination.limit, + skip=pagination.skip, + from_number=from_number, + to_number=to_number, + ) + + response = await self._execute_call(call=self._stub.GetContractTxs, request=request) + + return response + + async def fetch_blocks( + self, + before: Optional[int] = None, + after: Optional[int] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetBlocksRequest( + before=before, + after=after, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.GetBlocks, request=request) + + return response + + async def fetch_block(self, block_id: str) -> Dict[str, Any]: + request = exchange_explorer_pb.GetBlockRequest(id=block_id) + + response = await self._execute_call(call=self._stub.GetBlock, request=request) + + return response + + async def fetch_validators(self) -> Dict[str, Any]: + request = exchange_explorer_pb.GetValidatorsRequest() + + response = await self._execute_call(call=self._stub.GetValidators, request=request) + + return response + + async def fetch_validator(self, address: str) -> Dict[str, Any]: + request = exchange_explorer_pb.GetValidatorRequest(address=address) + + response = await self._execute_call(call=self._stub.GetValidator, request=request) + + return response + + async def fetch_validator_uptime(self, address: str) -> Dict[str, Any]: + request = exchange_explorer_pb.GetValidatorUptimeRequest(address=address) + + response = await self._execute_call(call=self._stub.GetValidatorUptime, request=request) + + return response + + async def fetch_txs( + self, + before: Optional[int] = None, + after: Optional[int] = None, + message_type: Optional[str] = None, + module: Optional[str] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + status: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetTxsRequest( + before=before, + after=after, + limit=pagination.limit, + skip=pagination.skip, + type=message_type, + module=module, + from_number=from_number, + to_number=to_number, + start_time=pagination.start_time, + end_time=pagination.end_time, + status=status, + ) + + response = await self._execute_call(call=self._stub.GetTxs, request=request) + + return response + + async def fetch_tx_by_tx_hash(self, tx_hash: str) -> Dict[str, Any]: + request = exchange_explorer_pb.GetTxByTxHashRequest(hash=tx_hash) + + response = await self._execute_call(call=self._stub.GetTxByTxHash, request=request) + + return response + + async def fetch_peggy_deposit_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetPeggyDepositTxsRequest( + sender=sender, + receiver=receiver, + limit=pagination.limit, + skip=pagination.skip, + ) + + response = await self._execute_call(call=self._stub.GetPeggyDepositTxs, request=request) + + return response + + async def fetch_peggy_withdrawal_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetPeggyWithdrawalTxsRequest( + sender=sender, + receiver=receiver, + limit=pagination.limit, + skip=pagination.skip, + ) + + response = await self._execute_call(call=self._stub.GetPeggyWithdrawalTxs, request=request) + + return response + + async def fetch_ibc_transfer_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + src_channel: Optional[str] = None, + src_port: Optional[str] = None, + dest_channel: Optional[str] = None, + dest_port: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetIBCTransferTxsRequest( + sender=sender, + receiver=receiver, + src_channel=src_channel, + src_port=src_port, + dest_channel=dest_channel, + dest_port=dest_port, + limit=pagination.limit, + skip=pagination.skip, + ) + + response = await self._execute_call(call=self._stub.GetIBCTransferTxs, request=request) + + return response + + async def fetch_wasm_codes( + self, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetWasmCodesRequest( + limit=pagination.limit, + from_number=from_number, + to_number=to_number, + ) + + response = await self._execute_call(call=self._stub.GetWasmCodes, request=request) + + return response + + async def fetch_wasm_code_by_id( + self, + code_id: int, + ) -> Dict[str, Any]: + request = exchange_explorer_pb.GetWasmCodeByIDRequest(code_id=code_id) + + response = await self._execute_call(call=self._stub.GetWasmCodeByID, request=request) + + return response + + async def fetch_wasm_contracts( + self, + code_id: Optional[int] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + assets_only: Optional[bool] = None, + label: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetWasmContractsRequest( + limit=pagination.limit, + code_id=code_id, + from_number=from_number, + to_number=to_number, + assets_only=assets_only, + skip=pagination.skip, + label=label, + ) + + response = await self._execute_call(call=self._stub.GetWasmContracts, request=request) + + return response + + async def fetch_wasm_contract_by_address( + self, + address: str, + ) -> Dict[str, Any]: + request = exchange_explorer_pb.GetWasmContractByAddressRequest(contract_address=address) + + response = await self._execute_call(call=self._stub.GetWasmContractByAddress, request=request) + + return response + + async def fetch_cw20_balance( + self, + address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetCw20BalanceRequest( + address=address, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.GetCw20Balance, request=request) + + return response + + async def fetch_relayers( + self, + market_ids: Optional[List[str]] = None, + ) -> Dict[str, Any]: + request = exchange_explorer_pb.RelayersRequest(market_i_ds=market_ids) + + response = await self._execute_call(call=self._stub.Relayers, request=request) + + return response + + async def fetch_bank_transfers( + self, + senders: Optional[List[str]] = None, + recipients: Optional[List[str]] = None, + is_community_pool_related: Optional[bool] = None, + address: Optional[List[str]] = None, + per_page: Optional[int] = None, + token: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetBankTransfersRequest( + senders=senders, + recipients=recipients, + is_community_pool_related=is_community_pool_related, + limit=pagination.limit, + skip=pagination.skip, + start_time=pagination.start_time, + end_time=pagination.end_time, + address=address, + per_page=per_page, + token=token, + ) + + response = await self._execute_call(call=self._stub.GetBankTransfers, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/composer.py b/pyinjective/composer.py index 726ec512..56491b50 100644 --- a/pyinjective/composer.py +++ b/pyinjective/composer.py @@ -17,6 +17,7 @@ from pyinjective.proto.cosmos.gov.v1beta1 import tx_pb2 as cosmos_gov_tx_pb from pyinjective.proto.cosmos.staking.v1beta1 import tx_pb2 as cosmos_staking_tx_pb from pyinjective.proto.cosmwasm.wasm.v1 import tx_pb2 as wasm_tx_pb +from pyinjective.proto.exchange import injective_explorer_rpc_pb2 as explorer_pb2 from pyinjective.proto.injective.auction.v1beta1 import tx_pb2 as injective_auction_tx_pb from pyinjective.proto.injective.exchange.v1beta1 import ( authz_pb2 as injective_authz_pb, @@ -52,6 +53,37 @@ "MsgInstantBinaryOptionsMarketLaunch": injective_exchange_tx_pb.MsgInstantBinaryOptionsMarketLaunchResponse, } +GRPC_MESSAGE_TYPE_TO_CLASS_MAP = { + "/injective.exchange.v1beta1.MsgCreateSpotLimitOrder": injective_exchange_tx_pb.MsgCreateSpotLimitOrder, + "/injective.exchange.v1beta1.MsgCreateSpotMarketOrder": injective_exchange_tx_pb.MsgCreateSpotMarketOrder, + "/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder": injective_exchange_tx_pb.MsgCreateDerivativeLimitOrder, + "/injective.exchange.v1beta1.MsgCreateDerivativeMarketOrder": injective_exchange_tx_pb.MsgCreateDerivativeMarketOrder, # noqa: 121 + "/injective.exchange.v1beta1.MsgCancelSpotOrder": injective_exchange_tx_pb.MsgCancelSpotOrder, + "/injective.exchange.v1beta1.MsgCancelDerivativeOrder": injective_exchange_tx_pb.MsgCancelDerivativeOrder, + "/injective.exchange.v1beta1.MsgBatchCancelSpotOrders": injective_exchange_tx_pb.MsgBatchCancelSpotOrders, + "/injective.exchange.v1beta1.MsgBatchCancelDerivativeOrders": injective_exchange_tx_pb.MsgBatchCancelDerivativeOrders, # noqa: 121 + "/injective.exchange.v1beta1.MsgBatchCreateSpotLimitOrders": injective_exchange_tx_pb.MsgBatchCreateSpotLimitOrders, + "/injective.exchange.v1beta1.MsgBatchCreateDerivativeLimitOrders": injective_exchange_tx_pb.MsgBatchCreateDerivativeLimitOrders, # noqa: 121 + "/injective.exchange.v1beta1.MsgBatchUpdateOrders": injective_exchange_tx_pb.MsgBatchUpdateOrders, + "/injective.exchange.v1beta1.MsgDeposit": injective_exchange_tx_pb.MsgDeposit, + "/injective.exchange.v1beta1.MsgWithdraw": injective_exchange_tx_pb.MsgWithdraw, + "/injective.exchange.v1beta1.MsgSubaccountTransfer": injective_exchange_tx_pb.MsgSubaccountTransfer, + "/injective.exchange.v1beta1.MsgLiquidatePosition": injective_exchange_tx_pb.MsgLiquidatePosition, + "/injective.exchange.v1beta1.MsgIncreasePositionMargin": injective_exchange_tx_pb.MsgIncreasePositionMargin, + "/injective.auction.v1beta1.MsgBid": injective_auction_tx_pb.MsgBid, + "/injective.exchange.v1beta1.MsgCreateBinaryOptionsLimitOrder": injective_exchange_tx_pb.MsgCreateBinaryOptionsLimitOrder, # noqa: 121 + "/injective.exchange.v1beta1.MsgCreateBinaryOptionsMarketOrder": injective_exchange_tx_pb.MsgCreateBinaryOptionsMarketOrder, # noqa: 121 + "/injective.exchange.v1beta1.MsgCancelBinaryOptionsOrder": injective_exchange_tx_pb.MsgCancelBinaryOptionsOrder, + "/injective.exchange.v1beta1.MsgAdminUpdateBinaryOptionsMarket": injective_exchange_tx_pb.MsgAdminUpdateBinaryOptionsMarket, # noqa: 121 + "/injective.exchange.v1beta1.MsgInstantBinaryOptionsMarketLaunch": injective_exchange_tx_pb.MsgInstantBinaryOptionsMarketLaunch, # noqa: 121 + "/cosmos.bank.v1beta1.MsgSend": cosmos_bank_tx_pb.MsgSend, + "/cosmos.authz.v1beta1.MsgGrant": cosmos_authz_tx_pb.MsgGrant, + "/cosmos.authz.v1beta1.MsgExec": cosmos_authz_tx_pb.MsgExec, + "/cosmos.authz.v1beta1.MsgRevoke": cosmos_authz_tx_pb.MsgRevoke, + "/injective.oracle.v1beta1.MsgRelayPriceFeedPrice": injective_oracle_tx_pb.MsgRelayPriceFeedPrice, + "/injective.oracle.v1beta1.MsgRelayProviderPrices": injective_oracle_tx_pb.MsgRelayProviderPrices, +} + class Composer: def __init__( @@ -1081,66 +1113,7 @@ def unpack_msg_exec_response(underlying_msg_type: str, msg_exec_response: Dict[s @staticmethod def UnpackTransactionMessages(transaction): meta_messages = json.loads(transaction.messages.decode()) - # fmt: off - header_map = { - "/injective.exchange.v1beta1.MsgCreateSpotLimitOrder": - injective_exchange_tx_pb.MsgCreateSpotLimitOrder, - "/injective.exchange.v1beta1.MsgCreateSpotMarketOrder": - injective_exchange_tx_pb.MsgCreateSpotMarketOrder, - "/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder": - injective_exchange_tx_pb.MsgCreateDerivativeLimitOrder, - "/injective.exchange.v1beta1.MsgCreateDerivativeMarketOrder": - injective_exchange_tx_pb.MsgCreateDerivativeMarketOrder, - "/injective.exchange.v1beta1.MsgCancelSpotOrder": - injective_exchange_tx_pb.MsgCancelSpotOrder, - "/injective.exchange.v1beta1.MsgCancelDerivativeOrder": - injective_exchange_tx_pb.MsgCancelDerivativeOrder, - "/injective.exchange.v1beta1.MsgBatchCancelSpotOrders": - injective_exchange_tx_pb.MsgBatchCancelSpotOrders, - "/injective.exchange.v1beta1.MsgBatchCancelDerivativeOrders": - injective_exchange_tx_pb.MsgBatchCancelDerivativeOrders, - "/injective.exchange.v1beta1.MsgBatchCreateSpotLimitOrders": - injective_exchange_tx_pb.MsgBatchCreateSpotLimitOrders, - "/injective.exchange.v1beta1.MsgBatchCreateDerivativeLimitOrders": - injective_exchange_tx_pb.MsgBatchCreateDerivativeLimitOrders, - "/injective.exchange.v1beta1.MsgBatchUpdateOrders": - injective_exchange_tx_pb.MsgBatchUpdateOrders, - "/injective.exchange.v1beta1.MsgDeposit": - injective_exchange_tx_pb.MsgDeposit, - "/injective.exchange.v1beta1.MsgWithdraw": - injective_exchange_tx_pb.MsgWithdraw, - "/injective.exchange.v1beta1.MsgSubaccountTransfer": - injective_exchange_tx_pb.MsgSubaccountTransfer, - "/injective.exchange.v1beta1.MsgLiquidatePosition": - injective_exchange_tx_pb.MsgLiquidatePosition, - "/injective.exchange.v1beta1.MsgIncreasePositionMargin": - injective_exchange_tx_pb.MsgIncreasePositionMargin, - "/injective.auction.v1beta1.MsgBid": - injective_auction_tx_pb.MsgBid, - "/injective.exchange.v1beta1.MsgCreateBinaryOptionsLimitOrder": - injective_exchange_tx_pb.MsgCreateBinaryOptionsLimitOrder, - "/injective.exchange.v1beta1.MsgCreateBinaryOptionsMarketOrder": - injective_exchange_tx_pb.MsgCreateBinaryOptionsMarketOrder, - "/injective.exchange.v1beta1.MsgCancelBinaryOptionsOrder": - injective_exchange_tx_pb.MsgCancelBinaryOptionsOrder, - "/injective.exchange.v1beta1.MsgAdminUpdateBinaryOptionsMarket": - injective_exchange_tx_pb.MsgAdminUpdateBinaryOptionsMarket, - "/injective.exchange.v1beta1.MsgInstantBinaryOptionsMarketLaunch": - injective_exchange_tx_pb.MsgInstantBinaryOptionsMarketLaunch, - "/cosmos.bank.v1beta1.MsgSend": - cosmos_bank_tx_pb.MsgSend, - "/cosmos.authz.v1beta1.MsgGrant": - cosmos_authz_tx_pb.MsgGrant, - "/cosmos.authz.v1beta1.MsgExec": - cosmos_authz_tx_pb.MsgExec, - "/cosmos.authz.v1beta1.MsgRevoke": - cosmos_authz_tx_pb.MsgRevoke, - "/injective.oracle.v1beta1.MsgRelayPriceFeedPrice": - injective_oracle_tx_pb.MsgRelayPriceFeedPrice, - "/injective.oracle.v1beta1.MsgRelayProviderPrices": - injective_oracle_tx_pb.MsgRelayProviderPrices, - } - # fmt: on + header_map = GRPC_MESSAGE_TYPE_TO_CLASS_MAP msgs = [] for msg in meta_messages: msg_as_string_dict = json.dumps(msg["value"]) @@ -1148,6 +1121,24 @@ def UnpackTransactionMessages(transaction): return msgs + @staticmethod + def unpack_transaction_messages(transaction_data: Dict[str, Any]) -> List[Dict[str, Any]]: + grpc_tx = explorer_pb2.TxDetailData() + json_format.ParseDict(js_dict=transaction_data, message=grpc_tx, ignore_unknown_fields=True) + meta_messages = json.loads(grpc_tx.messages.decode()) + msgs = [] + for msg in meta_messages: + msg_as_string_dict = json.dumps(msg["value"]) + grpc_message = json_format.Parse(msg_as_string_dict, GRPC_MESSAGE_TYPE_TO_CLASS_MAP[msg["type"]]()) + msgs.append( + { + "type": msg["type"], + "value": json_format.MessageToDict(message=grpc_message, including_default_value_fields=True), + } + ) + + return msgs + def _initialize_markets_and_tokens_from_files(self): config: ConfigParser = constant.CONFIGS[self.network] spot_markets = dict() diff --git a/tests/client/indexer/configurable_explorer_query_servicer.py b/tests/client/indexer/configurable_explorer_query_servicer.py new file mode 100644 index 00000000..37efc8da --- /dev/null +++ b/tests/client/indexer/configurable_explorer_query_servicer.py @@ -0,0 +1,101 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_explorer_rpc_pb2 as exchange_explorer_pb, + injective_explorer_rpc_pb2_grpc as exchange_explorer_grpc, +) + + +class ConfigurableExplorerQueryServicer(exchange_explorer_grpc.InjectiveExplorerRPCServicer): + def __init__(self): + super().__init__() + self.account_txs_responses = deque() + self.contract_txs_responses = deque() + self.blocks_responses = deque() + self.block_responses = deque() + self.validators_responses = deque() + self.validator_responses = deque() + self.validator_uptime_responses = deque() + self.txs_responses = deque() + self.tx_by_tx_hash_responses = deque() + self.peggy_deposit_txs_responses = deque() + self.peggy_withdrawal_txs_responses = deque() + self.ibc_transfer_txs_responses = deque() + self.wasm_codes_responses = deque() + self.wasm_code_by_id_responses = deque() + self.wasm_contracts_responses = deque() + self.wasm_contract_by_address_responses = deque() + self.cw20_balance_responses = deque() + self.relayers_responses = deque() + self.bank_transfers_responses = deque() + + async def GetAccountTxs(self, request: exchange_explorer_pb.GetAccountTxsRequest, context=None, metadata=None): + return self.account_txs_responses.pop() + + async def GetContractTxs(self, request: exchange_explorer_pb.GetContractTxsRequest, context=None, metadata=None): + return self.contract_txs_responses.pop() + + async def GetBlocks(self, request: exchange_explorer_pb.GetBlocksRequest, context=None, metadata=None): + return self.blocks_responses.pop() + + async def GetBlock(self, request: exchange_explorer_pb.GetBlockRequest, context=None, metadata=None): + return self.block_responses.pop() + + async def GetValidators(self, request: exchange_explorer_pb.GetValidatorsRequest, context=None, metadata=None): + return self.validators_responses.pop() + + async def GetValidator(self, request: exchange_explorer_pb.GetValidatorRequest, context=None, metadata=None): + return self.validator_responses.pop() + + async def GetValidatorUptime( + self, request: exchange_explorer_pb.GetValidatorUptimeRequest, context=None, metadata=None + ): + return self.validator_uptime_responses.pop() + + async def GetTxs(self, request: exchange_explorer_pb.GetTxsRequest, context=None, metadata=None): + return self.txs_responses.pop() + + async def GetTxByTxHash(self, request: exchange_explorer_pb.GetTxByTxHashRequest, context=None, metadata=None): + return self.tx_by_tx_hash_responses.pop() + + async def GetPeggyDepositTxs( + self, request: exchange_explorer_pb.GetPeggyDepositTxsRequest, context=None, metadata=None + ): + return self.peggy_deposit_txs_responses.pop() + + async def GetPeggyWithdrawalTxs( + self, request: exchange_explorer_pb.GetPeggyWithdrawalTxsRequest, context=None, metadata=None + ): + return self.peggy_withdrawal_txs_responses.pop() + + async def GetIBCTransferTxs( + self, request: exchange_explorer_pb.GetIBCTransferTxsRequest, context=None, metadata=None + ): + return self.ibc_transfer_txs_responses.pop() + + async def GetWasmCodes(self, request: exchange_explorer_pb.GetWasmCodesRequest, context=None, metadata=None): + return self.wasm_codes_responses.pop() + + async def GetWasmCodeByID(self, request: exchange_explorer_pb.GetWasmCodeByIDRequest, context=None, metadata=None): + return self.wasm_code_by_id_responses.pop() + + async def GetWasmContracts( + self, request: exchange_explorer_pb.GetWasmContractsRequest, context=None, metadata=None + ): + return self.wasm_contracts_responses.pop() + + async def GetWasmContractByAddress( + self, request: exchange_explorer_pb.GetWasmContractByAddressRequest, context=None, metadata=None + ): + return self.wasm_contract_by_address_responses.pop() + + async def GetCw20Balance(self, request: exchange_explorer_pb.GetCw20BalanceRequest, context=None, metadata=None): + return self.cw20_balance_responses.pop() + + async def Relayers(self, request: exchange_explorer_pb.RelayersRequest, context=None, metadata=None): + return self.relayers_responses.pop() + + async def GetBankTransfers( + self, request: exchange_explorer_pb.GetBankTransfersRequest, context=None, metadata=None + ): + return self.bank_transfers_responses.pop() diff --git a/tests/client/indexer/grpc/test_indexer_grpc_explorer_api.py b/tests/client/indexer/grpc/test_indexer_grpc_explorer_api.py new file mode 100644 index 00000000..3572174d --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_explorer_api.py @@ -0,0 +1,1582 @@ +import base64 + +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_explorer_api import IndexerGrpcExplorerApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_explorer_rpc_pb2 as exchange_explorer_pb +from tests.client.indexer.configurable_explorer_query_servicer import ConfigurableExplorerQueryServicer + + +@pytest.fixture +def explorer_servicer(): + return ConfigurableExplorerQueryServicer() + + +class TestIndexerGrpcExplorerApi: + @pytest.mark.asyncio + async def test_fetch_account_txs( + self, + explorer_servicer, + ): + code = 5 + coin = exchange_explorer_pb.CosmosCoin( + denom="inj", + amount="200000000000000", + ) + gas_fee = exchange_explorer_pb.GasFee( + amount=[coin], gas_limit=400000, payer="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", granter="test granter" + ) + event = exchange_explorer_pb.Event(type="test event type", attributes={"first_attribute": "attribute 1"}) + signature = exchange_explorer_pb.Signature( + pubkey="02c33c539e2aea9f97137e8168f6e22f57b829876823fa04b878a2b7c2010465d9", + address="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", + sequence=223460, + signature="gFXPJ5QENzq9SUHshE8g++aRLIlRCRVcOsYq+EOr3T4QgAAs5bVHf8NhugBjJP9B+AfQjQNNneHXPF9dEp4Uehs=", + ) + claim_id = 100 + + tx_data = exchange_explorer_pb.TxDetailData( + id="test id", + block_number=18138926, + block_timestamp="2023-11-07 23:19:55.371 +0000 UTC", + hash="0x3790ade2bea6c8605851ec89fa968adf2a2037a5ecac11ca95e99260508a3b7e", + code=code, + data=b"\022&\n$/cosmos.bank.v1beta1.MsgSendResponse", + info="test info", + gas_wanted=400000, + gas_used=93696, + gas_fee=gas_fee, + codespace="test codespace", + events=[event], + tx_type="injective-web3", + messages=b'[{"type":"/cosmos.bank.v1beta1.MsgSend","value":{' + b'"from_address":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex",' + b'"to_address":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc",' + b'"amount":[{"denom":"factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth",' + b'"amount":"100000000000000000"}]}}]', + signatures=[signature], + memo="test memo", + tx_number=221429, + block_unix_timestamp=1699399195371, + error_log="", + logs=b'[{"msg_index":0,"events":[{"type":"message","attributes":[{"key":"action",' + b'"value":"/cosmos.bank.v1beta1.MsgSend"},{"key":"sender",' + b'"value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},{"key":"module","value":"bank"}]},' + b'{"type":"coin_spent","attributes":[{"key":"spender",' + b'"value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},{"key":"amount",' + b'"value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}]},' + b'{"type":"coin_received","attributes":[{"key":"receiver",' + b'"value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},{"key":"amount",' + b'"value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}]},' + b'{"type":"transfer","attributes":[{"key":"recipient",' + b'"value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"message","attributes":[{"key":"sender",' + b'"value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"}]}]}]', + claim_ids=[claim_id], + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.account_txs_responses.append( + exchange_explorer_pb.GetAccountTxsResponse( + data=[tx_data], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_txs = await api.fetch_account_txs( + address="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", + before=221439, + after=221419, + message_type="cosmos.bank.v1beta1.MsgSend", + module="bank", + from_number=221419, + to_number=221439, + status="status", + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_txs = { + "data": [ + { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "code": tx_data.code, + "data": base64.b64encode(tx_data.data).decode(), + "info": tx_data.info, + "gasWanted": str(tx_data.gas_wanted), + "gasUsed": str(tx_data.gas_used), + "gasFee": { + "amount": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "gasLimit": str(gas_fee.gas_limit), + "payer": gas_fee.payer, + "granter": gas_fee.granter, + }, + "codespace": tx_data.codespace, + "events": [ + { + "type": event.type, + "attributes": event.attributes, + } + ], + "txType": tx_data.tx_type, + "messages": base64.b64encode(tx_data.messages).decode(), + "signatures": [ + { + "pubkey": signature.pubkey, + "address": signature.address, + "sequence": str(signature.sequence), + "signature": signature.signature, + } + ], + "memo": tx_data.memo, + "txNumber": str(tx_data.tx_number), + "blockUnixTimestamp": str(tx_data.block_unix_timestamp), + "errorLog": tx_data.error_log, + "logs": base64.b64encode(tx_data.logs).decode(), + "claimIds": [str(claim_id)], + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_txs == expected_txs + + @pytest.mark.asyncio + async def test_fetch_contract_txs( + self, + explorer_servicer, + ): + code = 5 + coin = exchange_explorer_pb.CosmosCoin( + denom="inj", + amount="200000000000000", + ) + gas_fee = exchange_explorer_pb.GasFee( + amount=[coin], gas_limit=400000, payer="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", granter="test granter" + ) + event = exchange_explorer_pb.Event(type="test event type", attributes={"first_attribute": "attribute 1"}) + signature = exchange_explorer_pb.Signature( + pubkey="02c33c539e2aea9f97137e8168f6e22f57b829876823fa04b878a2b7c2010465d9", + address="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", + sequence=223460, + signature="gFXPJ5QENzq9SUHshE8g++aRLIlRCRVcOsYq+EOr3T4QgAAs5bVHf8NhugBjJP9B+AfQjQNNneHXPF9dEp4Uehs=", + ) + claim_id = 100 + + tx_data = exchange_explorer_pb.TxDetailData( + id="test id", + block_number=18138926, + block_timestamp="2023-11-07 23:19:55.371 +0000 UTC", + hash="0x3790ade2bea6c8605851ec89fa968adf2a2037a5ecac11ca95e99260508a3b7e", + code=code, + data=b"\022&\n$/cosmos.bank.v1beta1.MsgSendResponse", + info="test info", + gas_wanted=400000, + gas_used=93696, + gas_fee=gas_fee, + codespace="test codespace", + events=[event], + tx_type="injective-web3", + messages=b'[{"type":"/cosmos.bank.v1beta1.MsgSend","value":{' + b'"from_address":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex",' + b'"to_address":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc",' + b'"amount":[{"denom":"factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth",' + b'"amount":"100000000000000000"}]}}]', + signatures=[signature], + memo="test memo", + tx_number=221429, + block_unix_timestamp=1699399195371, + error_log="", + logs=b'[{"msg_index":0,"events":[{"type":"message","attributes":[' + b'{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"module","value":"bank"}]},{"type":"coin_spent","attributes":[' + b'{"key":"spender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"coin_received","attributes":[' + b'{"key":"receiver","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"transfer","attributes":[' + b'{"key":"recipient","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"message","attributes":[' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"}]}]}]', + claim_ids=[claim_id], + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.contract_txs_responses.append( + exchange_explorer_pb.GetContractTxsResponse( + data=[tx_data], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_contract_txs = await api.fetch_contract_txs( + address="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", + from_number=221419, + to_number=221439, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_contract_txs = { + "data": [ + { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "code": tx_data.code, + "data": base64.b64encode(tx_data.data).decode(), + "info": tx_data.info, + "gasWanted": str(tx_data.gas_wanted), + "gasUsed": str(tx_data.gas_used), + "gasFee": { + "amount": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "gasLimit": str(gas_fee.gas_limit), + "payer": gas_fee.payer, + "granter": gas_fee.granter, + }, + "codespace": tx_data.codespace, + "events": [ + { + "type": event.type, + "attributes": event.attributes, + } + ], + "txType": tx_data.tx_type, + "messages": base64.b64encode(tx_data.messages).decode(), + "signatures": [ + { + "pubkey": signature.pubkey, + "address": signature.address, + "sequence": str(signature.sequence), + "signature": signature.signature, + } + ], + "memo": tx_data.memo, + "txNumber": str(tx_data.tx_number), + "blockUnixTimestamp": str(tx_data.block_unix_timestamp), + "errorLog": tx_data.error_log, + "logs": base64.b64encode(tx_data.logs).decode(), + "claimIds": [str(claim_id)], + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_contract_txs == expected_contract_txs + + @pytest.mark.asyncio + async def test_fetch_blocks( + self, + explorer_servicer, + ): + block_info = exchange_explorer_pb.BlockInfo( + height=19034578, + proposer="injvalcons18x63wcw5hjxlf535lgn4qy20yer7mm0qedu0la", + moniker="InjectiveNode1", + block_hash="0x7f7bfe8caaa0eed042315d1447ef1ed726a80f5da23fdbe6831fc66775197db1", + parent_hash="0x44287ba5fad21d0109a3ec6f19d447580763e5a709e5a5ceb767174e99ae3bd8", + num_pre_commits=20, + num_txs=4, + timestamp="2023-11-29 20:23:33.842 +0000 UTC", + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.blocks_responses.append( + exchange_explorer_pb.GetBlocksResponse( + data=[block_info], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_blocks = await api.fetch_blocks( + before=221419, + after=221439, + pagination=PaginationOption( + limit=100, + ), + ) + expected_blocks = { + "data": [ + { + "height": str(block_info.height), + "proposer": block_info.proposer, + "moniker": block_info.moniker, + "blockHash": block_info.block_hash, + "parentHash": block_info.parent_hash, + "numPreCommits": str(block_info.num_pre_commits), + "numTxs": str(block_info.num_txs), + "txs": [], + "timestamp": block_info.timestamp, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_blocks == expected_blocks + + @pytest.mark.asyncio + async def test_fetch_block( + self, + explorer_servicer, + ): + tx_data = exchange_explorer_pb.TxData( + id="tx id", + block_number=5825046, + block_timestamp="2022-12-11 22:06:49.182 +0000 UTC", + hash="0xbe8c8ca9a41196adf59b88fe9efd78e7532e04169152e779be3dc14ba7c360d9", + messages=b"null", + tx_number=994979, + tx_msg_types=b'["/injective.exchange.v1beta1.MsgCreateBinaryOptionsLimitOrder"]', + ) + block_info = exchange_explorer_pb.BlockDetailInfo( + height=19034578, + proposer="injvalcons18x63wcw5hjxlf535lgn4qy20yer7mm0qedu0la", + moniker="InjectiveNode1", + block_hash="0x7f7bfe8caaa0eed042315d1447ef1ed726a80f5da23fdbe6831fc66775197db1", + parent_hash="0x44287ba5fad21d0109a3ec6f19d447580763e5a709e5a5ceb767174e99ae3bd8", + num_pre_commits=20, + num_txs=4, + total_txs=5, + txs=[tx_data], + timestamp="2023-11-29 20:23:33.842 +0000 UTC", + ) + + explorer_servicer.block_responses.append( + exchange_explorer_pb.GetBlockResponse( + s="ok", + errmsg="test error message", + data=block_info, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_block = await api.fetch_block(block_id=str(block_info.height)) + expected_block = { + "s": "ok", + "errmsg": "test error message", + "data": { + "height": str(block_info.height), + "proposer": block_info.proposer, + "moniker": block_info.moniker, + "blockHash": block_info.block_hash, + "parentHash": block_info.parent_hash, + "numPreCommits": str(block_info.num_pre_commits), + "numTxs": str(block_info.num_txs), + "totalTxs": str(block_info.total_txs), + "txs": [ + { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "codespace": tx_data.codespace, + "messages": base64.b64encode(tx_data.messages).decode(), + "txNumber": str(tx_data.tx_number), + "errorLog": tx_data.error_log, + "code": tx_data.code, + "txMsgTypes": base64.b64encode(tx_data.tx_msg_types).decode(), + "logs": base64.b64encode(tx_data.logs).decode(), + "claimIds": tx_data.claim_ids, + } + ], + "timestamp": block_info.timestamp, + }, + } + + assert result_block == expected_block + + @pytest.mark.asyncio + async def test_fetch_validators( + self, + explorer_servicer, + ): + validator_description = exchange_explorer_pb.ValidatorDescription(moniker="InjectiveNode0") + validator = exchange_explorer_pb.Validator( + id="test id", + moniker="InjectiveNode0", + operator_address="injvaloper156t3yxd4udv0h9gwagfcmwnmm3quy0nph7tyh5", + consensus_address="injvalcons1xwg7xkmpqp8q804c37sa4dzyfwgnh4a74ll9pz", + jailed=False, + status=3, + tokens="200059138606549756596244963211573", + delegator_shares="200079146521201876783922319320744.623595039617821538", + description=validator_description, + unbonding_height=2489050, + unbonding_time="2022-09-18 14:44:56.825 +0000 UTC", + commission_rate="0.100000000000000000", + commission_max_rate="1.000000000000000000", + commission_max_change_rate="1.000000000000000000", + commission_update_time="2022-07-05 00:43:31.747 +0000 UTC", + proposed=4140681, + signed=10764141, + missed=0, + timestamp="2023-11-30 15:17:26.124 +0000 UTC", + uptime_percentage=99.906641771138965, + ) + + explorer_servicer.validators_responses.append( + exchange_explorer_pb.GetValidatorsResponse( + s="ok", + errmsg="test error message", + data=[validator], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_validators = await api.fetch_validators() + expected_validators = { + "s": "ok", + "errmsg": "test error message", + "data": [ + { + "id": validator.id, + "moniker": validator.moniker, + "operatorAddress": validator.operator_address, + "consensusAddress": validator.consensus_address, + "jailed": validator.jailed, + "status": validator.status, + "tokens": validator.tokens, + "delegatorShares": validator.delegator_shares, + "description": { + "moniker": validator_description.moniker, + "identity": validator_description.identity, + "website": validator_description.website, + "securityContact": validator_description.security_contact, + "details": validator_description.details, + "imageUrl": validator_description.image_url, + }, + "unbondingHeight": str(validator.unbonding_height), + "unbondingTime": validator.unbonding_time, + "commissionRate": validator.commission_rate, + "commissionMaxRate": validator.commission_max_rate, + "commissionMaxChangeRate": validator.commission_max_change_rate, + "commissionUpdateTime": validator.commission_update_time, + "proposed": str(validator.proposed), + "signed": str(validator.signed), + "missed": str(validator.missed), + "timestamp": validator.timestamp, + "uptimes": validator.uptimes, + "slashingEvents": validator.slashing_events, + "uptimePercentage": validator.uptime_percentage, + "imageUrl": validator.image_url, + }, + ], + } + + assert result_validators == expected_validators + + @pytest.mark.asyncio + async def test_fetch_validator( + self, + explorer_servicer, + ): + validator_description = exchange_explorer_pb.ValidatorDescription(moniker="InjectiveNode0") + validator = exchange_explorer_pb.Validator( + id="test id", + moniker="InjectiveNode0", + operator_address="injvaloper156t3yxd4udv0h9gwagfcmwnmm3quy0nph7tyh5", + consensus_address="injvalcons1xwg7xkmpqp8q804c37sa4dzyfwgnh4a74ll9pz", + jailed=False, + status=3, + tokens="200059138606549756596244963211573", + delegator_shares="200079146521201876783922319320744.623595039617821538", + description=validator_description, + unbonding_height=2489050, + unbonding_time="2022-09-18 14:44:56.825 +0000 UTC", + commission_rate="0.100000000000000000", + commission_max_rate="1.000000000000000000", + commission_max_change_rate="1.000000000000000000", + commission_update_time="2022-07-05 00:43:31.747 +0000 UTC", + proposed=4140681, + signed=10764141, + missed=0, + timestamp="2023-11-30 15:17:26.124 +0000 UTC", + uptime_percentage=99.906641771138965, + ) + + explorer_servicer.validator_responses.append( + exchange_explorer_pb.GetValidatorResponse( + s="ok", + errmsg="test error message", + data=validator, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_validator = await api.fetch_validator(address=validator.operator_address) + expected_validator = { + "s": "ok", + "errmsg": "test error message", + "data": { + "id": validator.id, + "moniker": validator.moniker, + "operatorAddress": validator.operator_address, + "consensusAddress": validator.consensus_address, + "jailed": validator.jailed, + "status": validator.status, + "tokens": validator.tokens, + "delegatorShares": validator.delegator_shares, + "description": { + "moniker": validator_description.moniker, + "identity": validator_description.identity, + "website": validator_description.website, + "securityContact": validator_description.security_contact, + "details": validator_description.details, + "imageUrl": validator_description.image_url, + }, + "unbondingHeight": str(validator.unbonding_height), + "unbondingTime": validator.unbonding_time, + "commissionRate": validator.commission_rate, + "commissionMaxRate": validator.commission_max_rate, + "commissionMaxChangeRate": validator.commission_max_change_rate, + "commissionUpdateTime": validator.commission_update_time, + "proposed": str(validator.proposed), + "signed": str(validator.signed), + "missed": str(validator.missed), + "timestamp": validator.timestamp, + "uptimes": validator.uptimes, + "slashingEvents": validator.slashing_events, + "uptimePercentage": validator.uptime_percentage, + "imageUrl": validator.image_url, + }, + } + + assert result_validator == expected_validator + + @pytest.mark.asyncio + async def test_fetch_validator_uptime( + self, + explorer_servicer, + ): + validator_uptime = exchange_explorer_pb.ValidatorUptime( + block_number=2489050, + status="3", + ) + + explorer_servicer.validator_uptime_responses.append( + exchange_explorer_pb.GetValidatorUptimeResponse( + s="ok", + errmsg="test error message", + data=[validator_uptime], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_validator = await api.fetch_validator_uptime(address="injvaloper156t3yxd4udv0h9gwagfcmwnmm3quy0nph7tyh5") + expected_validator = { + "s": "ok", + "errmsg": "test error message", + "data": [ + { + "blockNumber": str(validator_uptime.block_number), + "status": validator_uptime.status, + }, + ], + } + + assert result_validator == expected_validator + + @pytest.mark.asyncio + async def test_fetch_txs( + self, + explorer_servicer, + ): + code = 5 + claim_id = 100 + + tx_data = exchange_explorer_pb.TxData( + id="test id", + block_number=18138926, + block_timestamp="2023-11-07 23:19:55.371 +0000 UTC", + hash="0x3790ade2bea6c8605851ec89fa968adf2a2037a5ecac11ca95e99260508a3b7e", + codespace="test codespace", + messages=b'[{"type":"/cosmos.bank.v1beta1.MsgSend",' + b'"value":{"from_address":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex",' + b'"to_address":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc",' + b'"amount":[{"denom":"factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth",' + b'"amount":"100000000000000000"}]}}]', + tx_number=221429, + error_log="", + code=code, + tx_msg_types=b'["/injective.exchange.v1beta1.MsgCreateBinaryOptionsLimitOrder"]', + logs=b'[{"msg_index":0,"events":[{"type":"message","attributes":[' + b'{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"module","value":"bank"}]},{"type":"coin_spent","attributes":[' + b'{"key":"spender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"coin_received","attributes":[' + b'{"key":"receiver","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"transfer","attributes":[' + b'{"key":"recipient","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"message","attributes":[' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"}]}]}]', + claim_ids=[claim_id], + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.txs_responses.append( + exchange_explorer_pb.GetTxsResponse( + data=[tx_data], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_txs = await api.fetch_txs( + before=221439, + after=221419, + message_type="cosmos.bank.v1beta1.MsgSend", + module="bank", + from_number=221419, + to_number=221439, + status="status", + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_txs = { + "data": [ + { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "codespace": tx_data.codespace, + "messages": base64.b64encode(tx_data.messages).decode(), + "txNumber": str(tx_data.tx_number), + "errorLog": tx_data.error_log, + "code": tx_data.code, + "txMsgTypes": base64.b64encode(tx_data.tx_msg_types).decode(), + "logs": base64.b64encode(tx_data.logs).decode(), + "claimIds": [str(claim_id)], + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_txs == expected_txs + + @pytest.mark.asyncio + async def test_fetch_tx_by_hash( + self, + explorer_servicer, + ): + code = 5 + coin = exchange_explorer_pb.CosmosCoin( + denom="inj", + amount="200000000000000", + ) + gas_fee = exchange_explorer_pb.GasFee( + amount=[coin], gas_limit=400000, payer="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", granter="test granter" + ) + event = exchange_explorer_pb.Event(type="test event type", attributes={"first_attribute": "attribute 1"}) + signature = exchange_explorer_pb.Signature( + pubkey="02c33c539e2aea9f97137e8168f6e22f57b829876823fa04b878a2b7c2010465d9", + address="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", + sequence=223460, + signature="gFXPJ5QENzq9SUHshE8g++aRLIlRCRVcOsYq+EOr3T4QgAAs5bVHf8NhugBjJP9B+AfQjQNNneHXPF9dEp4Uehs=", + ) + claim_id = 100 + + tx_data = exchange_explorer_pb.TxDetailData( + id="test id", + block_number=18138926, + block_timestamp="2023-11-07 23:19:55.371 +0000 UTC", + hash="0x3790ade2bea6c8605851ec89fa968adf2a2037a5ecac11ca95e99260508a3b7e", + code=code, + data=b"\022&\n$/cosmos.bank.v1beta1.MsgSendResponse", + info="test info", + gas_wanted=400000, + gas_used=93696, + gas_fee=gas_fee, + codespace="test codespace", + events=[event], + tx_type="injective-web3", + messages=b'[{"type":"/cosmos.bank.v1beta1.MsgSend",' + b'"value":{"from_address":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex",' + b'"to_address":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc",' + b'"amount":[{"denom":"factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth",' + b'"amount":"100000000000000000"}]}}]', + signatures=[signature], + memo="test memo", + tx_number=221429, + block_unix_timestamp=1699399195371, + error_log="", + logs=b'[{"msg_index":0,"events":[{"type":"message","attributes":[' + b'{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"module","value":"bank"}]},{"type":"coin_spent","attributes":[' + b'{"key":"spender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"coin_received","attributes":[' + b'{"key":"receiver","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"transfer","attributes":[' + b'{"key":"recipient","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"message","attributes":[' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"}]}]}]', + claim_ids=[claim_id], + ) + + explorer_servicer.tx_by_tx_hash_responses.append( + exchange_explorer_pb.GetTxByTxHashResponse( + s="ok", + errmsg="test error message", + data=tx_data, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_tx = await api.fetch_tx_by_tx_hash(tx_hash=tx_data.hash) + expected_tx = { + "s": "ok", + "errmsg": "test error message", + "data": { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "code": tx_data.code, + "data": base64.b64encode(tx_data.data).decode(), + "info": tx_data.info, + "gasWanted": str(tx_data.gas_wanted), + "gasUsed": str(tx_data.gas_used), + "gasFee": { + "amount": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "gasLimit": str(gas_fee.gas_limit), + "payer": gas_fee.payer, + "granter": gas_fee.granter, + }, + "codespace": tx_data.codespace, + "events": [ + { + "type": event.type, + "attributes": event.attributes, + } + ], + "txType": tx_data.tx_type, + "messages": base64.b64encode(tx_data.messages).decode(), + "signatures": [ + { + "pubkey": signature.pubkey, + "address": signature.address, + "sequence": str(signature.sequence), + "signature": signature.signature, + } + ], + "memo": tx_data.memo, + "txNumber": str(tx_data.tx_number), + "blockUnixTimestamp": str(tx_data.block_unix_timestamp), + "errorLog": tx_data.error_log, + "logs": base64.b64encode(tx_data.logs).decode(), + "claimIds": [str(claim_id)], + }, + } + + assert result_tx == expected_tx + + @pytest.mark.asyncio + async def test_fetch_peggy_deposit_txs( + self, + explorer_servicer, + ): + tx_hash = "0x028a43ad2089cad45a8855143508f7381787d7f17cc19e3cda1bc2300c1d043f" + tx_data = exchange_explorer_pb.PeggyDepositTx( + sender="0x197E6c3f19951eA0bA90Ddf465bcC79790cDD12d", + receiver="inj1r9lxc0cej502pw5smh6xt0x8j7gvm5fdrj6xhk", + event_nonce=624, + event_height=10122722, + amount="500000000000000000", + denom="0xAD1794307245443B3Cb55d88e79EEE4d8a548C03", + orchestrator_address="inj1c8rpu79mr70hqsgzutdd6rhvzhej9vntm6fqku", + state="Completed", + claim_type=1, + tx_hashes=[tx_hash], + created_at="2023-11-28 16:55:54.841 +0000 UTC", + updated_at="2023-11-28 16:56:07.944 +0000 UTC", + ) + + explorer_servicer.peggy_deposit_txs_responses.append( + exchange_explorer_pb.GetPeggyDepositTxsResponse(field=[tx_data]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_tx = await api.fetch_peggy_deposit_txs( + sender=tx_data.sender, + receiver=tx_data.receiver, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_tx = { + "field": [ + { + "sender": tx_data.sender, + "receiver": tx_data.receiver, + "eventNonce": str(tx_data.event_nonce), + "eventHeight": str(tx_data.event_height), + "amount": tx_data.amount, + "denom": tx_data.denom, + "orchestratorAddress": tx_data.orchestrator_address, + "state": tx_data.state, + "claimType": tx_data.claim_type, + "txHashes": [tx_hash], + "createdAt": tx_data.created_at, + "updatedAt": tx_data.updated_at, + }, + ] + } + + assert result_tx == expected_tx + + @pytest.mark.asyncio + async def test_fetch_peggy_withdrawal_txs( + self, + explorer_servicer, + ): + tx_hash = "0x028a43ad2089cad45a8855143508f7381787d7f17cc19e3cda1bc2300c1d043f" + tx_data = exchange_explorer_pb.PeggyWithdrawalTx( + sender="0x197E6c3f19951eA0bA90Ddf465bcC79790cDD12d", + receiver="inj1r9lxc0cej502pw5smh6xt0x8j7gvm5fdrj6xhk", + amount="500000000000000000", + denom="0xAD1794307245443B3Cb55d88e79EEE4d8a548C03", + bridge_fee="575043128234617596", + outgoing_tx_id=1136, + batch_timeout=10125614, + batch_nonce=1600, + orchestrator_address="inj1c8rpu79mr70hqsgzutdd6rhvzhej9vntm6fqku", + event_nonce=624, + event_height=10122722, + state="Completed", + claim_type=1, + tx_hashes=[tx_hash], + created_at="2023-11-28 16:55:54.841 +0000 UTC", + updated_at="2023-11-28 16:56:07.944 +0000 UTC", + ) + + explorer_servicer.peggy_withdrawal_txs_responses.append( + exchange_explorer_pb.GetPeggyWithdrawalTxsResponse(field=[tx_data]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_tx = await api.fetch_peggy_withdrawal_txs( + sender=tx_data.sender, + receiver=tx_data.receiver, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_tx = { + "field": [ + { + "sender": tx_data.sender, + "receiver": tx_data.receiver, + "amount": tx_data.amount, + "denom": tx_data.denom, + "bridgeFee": tx_data.bridge_fee, + "outgoingTxId": str(tx_data.outgoing_tx_id), + "batchTimeout": str(tx_data.batch_timeout), + "batchNonce": str(tx_data.batch_nonce), + "orchestratorAddress": tx_data.orchestrator_address, + "eventNonce": str(tx_data.event_nonce), + "eventHeight": str(tx_data.event_height), + "state": tx_data.state, + "claimType": tx_data.claim_type, + "txHashes": [tx_hash], + "createdAt": tx_data.created_at, + "updatedAt": tx_data.updated_at, + }, + ] + } + + assert result_tx == expected_tx + + @pytest.mark.asyncio + async def test_fetch_ibc_transfer_txs( + self, + explorer_servicer, + ): + tx_hash = "0x028a43ad2089cad45a8855143508f7381787d7f17cc19e3cda1bc2300c1d043f" + tx_data = exchange_explorer_pb.IBCTransferTx( + sender="0x197E6c3f19951eA0bA90Ddf465bcC79790cDD12d", + receiver="inj1r9lxc0cej502pw5smh6xt0x8j7gvm5fdrj6xhk", + source_port="transfer", + source_channel="channel-74", + destination_port="transfer", + destination_channel="channel-33", + amount="500000000000000000", + denom="0xAD1794307245443B3Cb55d88e79EEE4d8a548C03", + timeout_height="0-0", + timeout_timestamp=1701460751755119600, + packet_sequence=16607, + data_hex=b"7b22616d6f756e74223a2231303030303030222c2264656e6f6d223a227472616e736665722f6368616e6e656c2d3734" + b"2f756e6f6973222c227265636569766572223a226e6f6973316d7675757067726537706a78336b35746d353732396672" + b"6b6e396e766a75367067737861776334377067616d63747970647a6c736d3768673930222c2273656e646572223a2269" + b"6e6a31346e656e6474737a306334306e3778747a776b6a6d646338646b757a3833356a64796478686e227d", + state="Completed", + tx_hashes=[tx_hash], + created_at="2023-11-28 16:55:54.841 +0000 UTC", + updated_at="2023-11-28 16:56:07.944 +0000 UTC", + ) + + explorer_servicer.ibc_transfer_txs_responses.append( + exchange_explorer_pb.GetIBCTransferTxsResponse(field=[tx_data]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_tx = await api.fetch_ibc_transfer_txs( + sender=tx_data.sender, + receiver=tx_data.receiver, + src_channel=tx_data.source_channel, + src_port=tx_data.source_port, + dest_channel=tx_data.destination_channel, + dest_port=tx_data.destination_port, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_tx = { + "field": [ + { + "sender": tx_data.sender, + "receiver": tx_data.receiver, + "sourcePort": tx_data.source_port, + "sourceChannel": tx_data.source_channel, + "destinationPort": tx_data.destination_port, + "destinationChannel": tx_data.destination_channel, + "amount": tx_data.amount, + "denom": tx_data.denom, + "timeoutHeight": tx_data.timeout_height, + "timeoutTimestamp": str(tx_data.timeout_timestamp), + "packetSequence": str(tx_data.packet_sequence), + "dataHex": base64.b64encode(tx_data.data_hex).decode(), + "state": tx_data.state, + "txHashes": [tx_hash], + "createdAt": tx_data.created_at, + "updatedAt": tx_data.updated_at, + }, + ] + } + + assert result_tx == expected_tx + + @pytest.mark.asyncio + async def test_fetch_wasm_codes( + self, + explorer_servicer, + ): + checksum = exchange_explorer_pb.Checksum( + algorithm="sha256", + hash="0xadecb2d943c03eeee77e111791df61198a9dee097f47f14a811b8f9657122624", + ) + permission = exchange_explorer_pb.ContractPermission( + access_type=3, + address="test address", + ) + wasm_code = exchange_explorer_pb.WasmCode( + code_id=245, + tx_hash="0xa5da295f9252dc932861be6f2a4dbc9a8c0f44bb42a473ded5ec349407a1c708", + checksum=checksum, + created_at=1701373663980, + contract_type="test contract type", + version="test version", + permission=permission, + code_schema="test code schema", + code_view="test code view", + instantiates=0, + creator="inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c", + code_number=253, + proposal_id=0, + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.wasm_codes_responses.append( + exchange_explorer_pb.GetWasmCodesResponse(paging=paging, data=[wasm_code]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_codes = await api.fetch_wasm_codes( + from_number=1, + to_number=1000, + pagination=PaginationOption( + limit=100, + ), + ) + expected_wasm_codes = { + "data": [ + { + "codeId": str(wasm_code.code_id), + "txHash": wasm_code.tx_hash, + "checksum": { + "algorithm": checksum.algorithm, + "hash": checksum.hash, + }, + "createdAt": str(wasm_code.created_at), + "contractType": wasm_code.contract_type, + "version": wasm_code.version, + "permission": { + "accessType": permission.access_type, + "address": permission.address, + }, + "codeSchema": wasm_code.code_schema, + "codeView": wasm_code.code_view, + "instantiates": str(wasm_code.instantiates), + "creator": wasm_code.creator, + "codeNumber": str(wasm_code.code_number), + "proposalId": str(wasm_code.proposal_id), + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_wasm_codes == expected_wasm_codes + + @pytest.mark.asyncio + async def test_fetch_wasm_code_by_id( + self, + explorer_servicer, + ): + checksum = exchange_explorer_pb.Checksum( + algorithm="sha256", + hash="0xadecb2d943c03eeee77e111791df61198a9dee097f47f14a811b8f9657122624", + ) + permission = exchange_explorer_pb.ContractPermission( + access_type=3, + address="test address", + ) + wasm_code = exchange_explorer_pb.GetWasmCodeByIDResponse( + code_id=245, + tx_hash="0xa5da295f9252dc932861be6f2a4dbc9a8c0f44bb42a473ded5ec349407a1c708", + checksum=checksum, + created_at=1701373663980, + contract_type="test contract type", + version="test version", + permission=permission, + code_schema="test code schema", + code_view="test code view", + instantiates=0, + creator="inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c", + code_number=253, + proposal_id=0, + ) + + explorer_servicer.wasm_code_by_id_responses.append(wasm_code) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_code = await api.fetch_wasm_code_by_id(code_id=wasm_code.code_id) + expected_wasm_code = { + "codeId": str(wasm_code.code_id), + "txHash": wasm_code.tx_hash, + "checksum": { + "algorithm": checksum.algorithm, + "hash": checksum.hash, + }, + "createdAt": str(wasm_code.created_at), + "contractType": wasm_code.contract_type, + "version": wasm_code.version, + "permission": { + "accessType": permission.access_type, + "address": permission.address, + }, + "codeSchema": wasm_code.code_schema, + "codeView": wasm_code.code_view, + "instantiates": str(wasm_code.instantiates), + "creator": wasm_code.creator, + "codeNumber": str(wasm_code.code_number), + "proposalId": str(wasm_code.proposal_id), + } + + assert result_wasm_code == expected_wasm_code + + @pytest.mark.asyncio + async def test_fetch_wasm_contracts( + self, + explorer_servicer, + ): + wasm_contract = exchange_explorer_pb.WasmContract( + label="Talis candy machine", + address="inj1t4lnxfu9gtyd50uqmf0ahpwk3vtg5yk9pe7uj4", + tx_hash="0x7462ce393fd7691c5179107dcd5ee47c79e7a348538c0c976e160bbbfdae338c", + creator="inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj", + executes=23, + instantiated_at=1701320950004, + init_message='{"admin":"inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3","codeId":"104",' + '"label":"Talis candy machine","msg":"",' + '"sender":"inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj","fundsList":[],' + '"contract_address":"inj1mhsrt6ulz07wnesppy39wwygjntk0stmk39ftg",' + '"owner":"inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj",' + '"fee_collector":"inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3",' + '"operator_pubkey":"Aq9ExLymJrae0ol4Pq13vZkDARZeunbFWJGXgsHtkzkx",' + '"public_phase":{"id":0,"private":false,"start":1701363602,"end":1701489600,' + '"price":{"native":[{"denom":"inj","amount":"100000000000000000"}]},"mint_limit":5},' + '"reserved_tokens":11,"total_tokens":111}', + last_executed_at=1701395446228, + funds=[], + code_id=104, + admin="inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3", + current_migrate_message="", + contract_number=1037, + version="test version", + type="test_type", + proposal_id=0, + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.wasm_contracts_responses.append( + exchange_explorer_pb.GetWasmContractsResponse(paging=paging, data=[wasm_contract]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_contracts = await api.fetch_wasm_contracts( + code_id=wasm_contract.code_id, + from_number=1, + to_number=1000, + assets_only=False, + label=wasm_contract.label, + pagination=PaginationOption( + limit=100, + skip=10, + ), + ) + expected_wasm_contracts = { + "data": [ + { + "label": wasm_contract.label, + "address": wasm_contract.address, + "txHash": wasm_contract.tx_hash, + "creator": wasm_contract.creator, + "executes": str(wasm_contract.executes), + "instantiatedAt": str(wasm_contract.instantiated_at), + "initMessage": wasm_contract.init_message, + "lastExecutedAt": str(wasm_contract.last_executed_at), + "funds": wasm_contract.funds, + "codeId": str(wasm_contract.code_id), + "admin": wasm_contract.admin, + "currentMigrateMessage": wasm_contract.current_migrate_message, + "contractNumber": str(wasm_contract.contract_number), + "version": wasm_contract.version, + "type": wasm_contract.type, + "proposalId": str(wasm_contract.proposal_id), + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_wasm_contracts == expected_wasm_contracts + + @pytest.mark.asyncio + async def test_fetch_wasm_contract_by_address( + self, + explorer_servicer, + ): + wasm_contract = exchange_explorer_pb.GetWasmContractByAddressResponse( + label="Talis candy machine", + address="inj1t4lnxfu9gtyd50uqmf0ahpwk3vtg5yk9pe7uj4", + tx_hash="0x7462ce393fd7691c5179107dcd5ee47c79e7a348538c0c976e160bbbfdae338c", + creator="inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj", + executes=23, + instantiated_at=1701320950004, + init_message='{"admin":"inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3","codeId":"104",' + '"label":"Talis candy machine","msg":"","sender":"inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj",' + '"fundsList":[],"contract_address":"inj1mhsrt6ulz07wnesppy39wwygjntk0stmk39ftg",' + '"owner":"inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj",' + '"fee_collector":"inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3",' + '"operator_pubkey":"Aq9ExLymJrae0ol4Pq13vZkDARZeunbFWJGXgsHtkzkx",' + '"public_phase":{"id":0,"private":false,"start":1701363602,"end":1701489600,' + '"price":{"native":[{"denom":"inj","amount":"100000000000000000"}]},"mint_limit":5},' + '"reserved_tokens":11,"total_tokens":111}', + last_executed_at=1701395446228, + funds=[], + code_id=104, + admin="inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3", + current_migrate_message="", + contract_number=1037, + version="test version", + type="test_type", + proposal_id=0, + ) + + explorer_servicer.wasm_contract_by_address_responses.append(wasm_contract) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_contract = await api.fetch_wasm_contract_by_address(address=wasm_contract.address) + expected_wasm_contract = { + "label": wasm_contract.label, + "address": wasm_contract.address, + "txHash": wasm_contract.tx_hash, + "creator": wasm_contract.creator, + "executes": str(wasm_contract.executes), + "instantiatedAt": str(wasm_contract.instantiated_at), + "initMessage": wasm_contract.init_message, + "lastExecutedAt": str(wasm_contract.last_executed_at), + "funds": wasm_contract.funds, + "codeId": str(wasm_contract.code_id), + "admin": wasm_contract.admin, + "currentMigrateMessage": wasm_contract.current_migrate_message, + "contractNumber": str(wasm_contract.contract_number), + "version": wasm_contract.version, + "type": wasm_contract.type, + "proposalId": str(wasm_contract.proposal_id), + } + + assert result_wasm_contract == expected_wasm_contract + + @pytest.mark.asyncio + async def test_fetch_cw20_balance( + self, + explorer_servicer, + ): + token_info = exchange_explorer_pb.Cw20TokenInfo( + name="Tether", + symbol="USDT", + decimals=6, + total_supply="100000000000", + ) + marketing_info = exchange_explorer_pb.Cw20MarketingInfo( + project="Tether", + description="Tether project", + logo="test logo", + marketing=b"Test marketing info", + ) + cw20_metadata = exchange_explorer_pb.Cw20Metadata( + token_info=token_info, + marketing_info=marketing_info, + ) + wasm_balance = exchange_explorer_pb.WasmCw20Balance( + account="0xaf79152ac5df276d9a8e1e2e22822f9713474902", + balance="1000", + contract_address="inj1t4lnxfu9gtyd50uqmf0ahpwk3vtg5yk9pe7uj4", + cw20_metadata=cw20_metadata, + updated_at=1701395446228, + ) + + explorer_servicer.cw20_balance_responses.append( + exchange_explorer_pb.GetCw20BalanceResponse(field=[wasm_balance]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_contract = await api.fetch_cw20_balance( + address=wasm_balance.account, + pagination=PaginationOption( + limit=100, + ), + ) + expected_wasm_contract = { + "field": [ + { + "account": wasm_balance.account, + "balance": wasm_balance.balance, + "contractAddress": wasm_balance.contract_address, + "cw20Metadata": { + "tokenInfo": { + "name": token_info.name, + "symbol": token_info.symbol, + "decimals": str(token_info.decimals), + "totalSupply": token_info.total_supply, + }, + "marketingInfo": { + "project": marketing_info.project, + "description": marketing_info.description, + "logo": marketing_info.logo, + "marketing": base64.b64encode(marketing_info.marketing).decode(), + }, + }, + "updatedAt": str(wasm_balance.updated_at), + }, + ] + } + + assert result_wasm_contract == expected_wasm_contract + + @pytest.mark.asyncio + async def test_fetch_relayers( + self, + explorer_servicer, + ): + relayer = exchange_explorer_pb.Relayer( + name="Injdojo", + cta="https://injdojo.exchange", + ) + relayers = exchange_explorer_pb.RelayerMarkets( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", relayers=[relayer] + ) + + explorer_servicer.relayers_responses.append(exchange_explorer_pb.RelayersResponse(field=[relayers])) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_contract = await api.fetch_relayers( + market_ids=[relayers.market_id], + ) + expected_wasm_contract = { + "field": [ + { + "marketId": relayers.market_id, + "relayers": [ + { + "name": relayer.name, + "cta": relayer.cta, + }, + ], + }, + ] + } + + assert result_wasm_contract == expected_wasm_contract + + @pytest.mark.asyncio + async def test_fetch_bank_transfers( + self, + explorer_servicer, + ): + coin = exchange_explorer_pb.Coin( + denom="inj", + amount="200000000000000", + ) + bank_transfer = exchange_explorer_pb.BankTransfer( + sender="inj17xpfvakm2amg962yls6f84z3kell8c5l6s5ye9", + recipient="inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8", + amounts=[coin], + block_number=52990746, + block_timestamp="2023-12-01 14:25:28.266 +0000 UTC", + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.bank_transfers_responses.append( + exchange_explorer_pb.GetBankTransfersResponse(paging=paging, data=[bank_transfer]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_transfers = await api.fetch_bank_transfers( + senders=[bank_transfer.sender], + recipients=[bank_transfer.recipient], + is_community_pool_related=False, + address=["inj1t4lnxfu9gtyd50uqmf0ahpwk3vtg5yk9pe7uj4"], + per_page=20, + token="inj", + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_transfers = { + "data": [ + { + "sender": bank_transfer.sender, + "recipient": bank_transfer.recipient, + "amounts": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "blockNumber": str(bank_transfer.block_number), + "blockTimestamp": bank_transfer.block_timestamp, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_transfers == expected_transfers + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index f20f720c..1026f101 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -11,6 +11,7 @@ injective_accounts_rpc_pb2 as exchange_accounts_pb, injective_auction_rpc_pb2 as exchange_auction_pb, injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb, + injective_explorer_rpc_pb2 as exchange_explorer_pb, injective_insurance_rpc_pb2 as exchange_insurance_pb, injective_meta_rpc_pb2 as exchange_meta_pb, injective_oracle_rpc_pb2 as exchange_oracle_pb, @@ -24,6 +25,7 @@ from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer from tests.client.indexer.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer from tests.client.indexer.configurable_derivative_query_servicer import ConfigurableDerivativeQueryServicer +from tests.client.indexer.configurable_explorer_query_servicer import ConfigurableExplorerQueryServicer from tests.client.indexer.configurable_insurance_query_servicer import ConfigurableInsuranceQueryServicer from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer @@ -62,6 +64,11 @@ def derivative_servicer(): return ConfigurableDerivativeQueryServicer() +@pytest.fixture +def explorer_servicer(): + return ConfigurableExplorerQueryServicer() + + @pytest.fixture def insurance_servicer(): return ConfigurableInsuranceQueryServicer() @@ -1418,3 +1425,147 @@ async def test_stream_account_portfolio_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use listen_account_portfolio_updates instead" + + @pytest.mark.asyncio + async def test_get_account_txs_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.account_txs_responses.append(exchange_explorer_pb.GetAccountTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_account_txs(address="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_account_txs instead" + + @pytest.mark.asyncio + async def test_get_blocks_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.blocks_responses.append(exchange_explorer_pb.GetBlocksResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_blocks() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_blocks instead" + + @pytest.mark.asyncio + async def test_get_block_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.block_responses.append(exchange_explorer_pb.GetBlockResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_block(block_height="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_block instead" + + @pytest.mark.asyncio + async def test_get_txs_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.txs_responses.append(exchange_explorer_pb.GetTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_txs() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_txs instead" + + @pytest.mark.asyncio + async def test_get_tx_by_hash_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.tx_by_tx_hash_responses.append(exchange_explorer_pb.GetTxByTxHashResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_tx_by_hash(tx_hash="") + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_tx_by_tx_hash instead" + + @pytest.mark.asyncio + async def test_get_peggy_deposits_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.peggy_deposit_txs_responses.append(exchange_explorer_pb.GetPeggyDepositTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_peggy_deposits() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_peggy_deposit_txs instead" + + @pytest.mark.asyncio + async def test_get_peggy_withdrawals_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.peggy_withdrawal_txs_responses.append(exchange_explorer_pb.GetPeggyWithdrawalTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_peggy_withdrawals() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_peggy_withdrawal_txs instead" + + @pytest.mark.asyncio + async def test_get_ibc_transfers_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.ibc_transfer_txs_responses.append(exchange_explorer_pb.GetIBCTransferTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_ibc_transfers() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_ibc_transfer_txs instead" From d65cb2e7f51cd70ebb220d1daca4bc5059d7ff32 Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 1 Dec 2023 13:18:02 -0300 Subject: [PATCH 23/27] (feat) Added low level API component for exchange explorer streams. Added unit tests for the new functionality. Included new functions in AsyncClient using the low level API components, and marked the old functions as deprecated. Updated the example scripts. --- .../explorer_rpc/6_StreamTxs.py | 29 +++- .../explorer_rpc/7_StreamBlocks.py | 29 +++- pyinjective/async_client.py | 39 +++++ .../indexer_grpc_explorer_stream.py | 47 ++++++ .../configurable_explorer_query_servicer.py | 11 ++ .../test_indexer_grpc_explorer_stream.py | 138 ++++++++++++++++++ .../test_async_client_deprecation_warnings.py | 36 +++++ 7 files changed, 323 insertions(+), 6 deletions(-) create mode 100644 pyinjective/client/indexer/grpc_stream/indexer_grpc_explorer_stream.py create mode 100644 tests/client/indexer/stream_grpc/test_indexer_grpc_explorer_stream.py diff --git a/examples/exchange_client/explorer_rpc/6_StreamTxs.py b/examples/exchange_client/explorer_rpc/6_StreamTxs.py index f96a595e..07851daa 100644 --- a/examples/exchange_client/explorer_rpc/6_StreamTxs.py +++ b/examples/exchange_client/explorer_rpc/6_StreamTxs.py @@ -1,16 +1,39 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def tx_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to txs updates ({exception})") + + +def stream_closed_processor(): + print("The txs updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - stream_txs = await client.stream_txs() - async for tx in stream_txs: - print(tx) + + task = asyncio.get_event_loop().create_task( + client.listen_txs_updates( + callback=tx_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/explorer_rpc/7_StreamBlocks.py b/examples/exchange_client/explorer_rpc/7_StreamBlocks.py index f5169511..b9665742 100644 --- a/examples/exchange_client/explorer_rpc/7_StreamBlocks.py +++ b/examples/exchange_client/explorer_rpc/7_StreamBlocks.py @@ -1,16 +1,39 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def block_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to blocks updates ({exception})") + + +def stream_closed_processor(): + print("The blocks updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - stream_blocks = await client.stream_blocks() - async for block in stream_blocks: - print(block) + + task = asyncio.get_event_loop().create_task( + client.listen_blocks_updates( + callback=block_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index d78bb862..aa144ce1 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -24,6 +24,7 @@ from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_auction_stream import IndexerGrpcAuctionStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_derivative_stream import IndexerGrpcDerivativeStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_explorer_stream import IndexerGrpcExplorerStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_oracle_stream import IndexerGrpcOracleStream from pyinjective.client.indexer.grpc_stream.indexer_grpc_portfolio_stream import IndexerGrpcPortfolioStream @@ -278,6 +279,12 @@ def __init__( metadata_query_provider=self._explorer_cookie_metadata_requestor ), ) + self.exchange_explorer_stream_api = IndexerGrpcExplorerStream( + channel=self.explorer_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._explorer_cookie_metadata_requestor + ), + ) async def all_tokens(self) -> Dict[str, Token]: if self._tokens is None: @@ -729,13 +736,45 @@ async def fetch_txs( ) async def stream_txs(self): + """ + This method is deprecated and will be removed soon. Please use `listen_txs_updates` instead + """ + warn("This method is deprecated. Use listen_txs_updates instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.StreamTxsRequest() return self.stubExplorer.StreamTxs(req) + async def listen_txs_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_explorer_stream_api.stream_txs( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def stream_blocks(self): + """ + This method is deprecated and will be removed soon. Please use `listen_blocks_updates` instead + """ + warn("This method is deprecated. Use listen_blocks_updates instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.StreamBlocksRequest() return self.stubExplorer.StreamBlocks(req) + async def listen_blocks_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_explorer_stream_api.stream_blocks( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def get_peggy_deposits(self, **kwargs): """ This method is deprecated and will be removed soon. Please use `fetch_peggy_deposit_txs` instead diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_explorer_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_explorer_stream.py new file mode 100644 index 00000000..aa4a69ae --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_explorer_stream.py @@ -0,0 +1,47 @@ +from typing import Callable, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_explorer_rpc_pb2 as exchange_explorer_pb, + injective_explorer_rpc_pb2_grpc as exchange_explorer_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcExplorerStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_explorer_grpc.InjectiveExplorerRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_txs( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_explorer_pb.StreamTxsRequest() + + await self._assistant.listen_stream( + call=self._stub.StreamTxs, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_blocks( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_explorer_pb.StreamBlocksRequest() + + await self._assistant.listen_stream( + call=self._stub.StreamBlocks, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/tests/client/indexer/configurable_explorer_query_servicer.py b/tests/client/indexer/configurable_explorer_query_servicer.py index 37efc8da..ce240bf3 100644 --- a/tests/client/indexer/configurable_explorer_query_servicer.py +++ b/tests/client/indexer/configurable_explorer_query_servicer.py @@ -29,6 +29,9 @@ def __init__(self): self.relayers_responses = deque() self.bank_transfers_responses = deque() + self.stream_txs_responses = deque() + self.stream_blocks_responses = deque() + async def GetAccountTxs(self, request: exchange_explorer_pb.GetAccountTxsRequest, context=None, metadata=None): return self.account_txs_responses.pop() @@ -99,3 +102,11 @@ async def GetBankTransfers( self, request: exchange_explorer_pb.GetBankTransfersRequest, context=None, metadata=None ): return self.bank_transfers_responses.pop() + + async def StreamTxs(self, request: exchange_explorer_pb.StreamTxsRequest, context=None, metadata=None): + for event in self.stream_txs_responses: + yield event + + async def StreamBlocks(self, request: exchange_explorer_pb.StreamBlocksRequest, context=None, metadata=None): + for event in self.stream_blocks_responses: + yield event diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_explorer_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_explorer_stream.py new file mode 100644 index 00000000..98eed906 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_explorer_stream.py @@ -0,0 +1,138 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_explorer_stream import IndexerGrpcExplorerStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_explorer_rpc_pb2 as exchange_explorer_pb +from tests.client.indexer.configurable_explorer_query_servicer import ConfigurableExplorerQueryServicer + + +@pytest.fixture +def explorer_servicer(): + return ConfigurableExplorerQueryServicer() + + +class TestIndexerGrpcAuctionStream: + @pytest.mark.asyncio + async def test_stream_txs( + self, + explorer_servicer, + ): + code = 5 + claim_id = 100 + tx_data = exchange_explorer_pb.StreamTxsResponse( + id="test id", + block_number=18138926, + block_timestamp="2023-11-07 23:19:55.371 +0000 UTC", + hash="0x3790ade2bea6c8605851ec89fa968adf2a2037a5ecac11ca95e99260508a3b7e", + codespace="test codespace", + messages='[{"type":"/cosmos.bank.v1beta1.MsgSend",' + '"value":{"from_address":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex",' + '"to_address":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc",' + '"amount":[{"denom":"factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth",' + '"amount":"100000000000000000"}]}}]', + tx_number=221429, + error_log="", + code=code, + claim_ids=[claim_id], + ) + + explorer_servicer.stream_txs_responses.append(tx_data) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + txs_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: txs_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_txs( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "codespace": tx_data.codespace, + "messages": tx_data.messages, + "txNumber": str(tx_data.tx_number), + "errorLog": tx_data.error_log, + "code": tx_data.code, + "claimIds": [str(claim_id)], + } + + first_update = await asyncio.wait_for(txs_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_blocks( + self, + explorer_servicer, + ): + block_info = exchange_explorer_pb.StreamBlocksResponse( + height=19034578, + proposer="injvalcons18x63wcw5hjxlf535lgn4qy20yer7mm0qedu0la", + moniker="InjectiveNode1", + block_hash="0x7f7bfe8caaa0eed042315d1447ef1ed726a80f5da23fdbe6831fc66775197db1", + parent_hash="0x44287ba5fad21d0109a3ec6f19d447580763e5a709e5a5ceb767174e99ae3bd8", + num_pre_commits=20, + num_txs=4, + timestamp="2023-11-29 20:23:33.842 +0000 UTC", + ) + + explorer_servicer.stream_blocks_responses.append(block_info) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + blocks_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: blocks_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_blocks( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "height": str(block_info.height), + "proposer": block_info.proposer, + "moniker": block_info.moniker, + "blockHash": block_info.block_hash, + "parentHash": block_info.parent_hash, + "numPreCommits": str(block_info.num_pre_commits), + "numTxs": str(block_info.num_txs), + "txs": [], + "timestamp": block_info.timestamp, + } + + first_update = await asyncio.wait_for(blocks_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 1026f101..f29db5dd 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -1569,3 +1569,39 @@ async def test_get_ibc_transfers_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_ibc_transfer_txs instead" + + @pytest.mark.asyncio + async def test_stream_txs_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubPortfolio = explorer_servicer + explorer_servicer.stream_txs_responses.append(exchange_explorer_pb.StreamTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_txs() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_txs_updates instead" + + @pytest.mark.asyncio + async def test_stream_blocks_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubPortfolio = explorer_servicer + explorer_servicer.stream_blocks_responses.append(exchange_explorer_pb.StreamBlocksResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_blocks() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_blocks_updates instead" From f32ec5e12a07edc52d2eb4b8895a449b30fb8518 Mon Sep 17 00:00:00 2001 From: abel Date: Sat, 2 Dec 2023 01:58:37 -0300 Subject: [PATCH 24/27] (feat) Added low level API component for chain stream. Added unit tests for the new functionality. Included new functions in AsyncClient using the low level API components, and marked the old functions as deprecated. Updated the example scripts. --- examples/chain_client/49_ChainStream.py | 52 ++- pyinjective/async_client.py | 44 ++ .../client/chain/grpc_stream/__init__.py | 0 .../grpc_stream/chain_grpc_chain_stream.py | 49 +++ tests/client/chain/stream_grpc/__init__.py | 0 ...onfigurable_chain_stream_query_servicer.py | 13 + .../test_chain_grpc_chain_stream.py | 408 ++++++++++++++++++ .../test_async_client_deprecation_warnings.py | 25 ++ 8 files changed, 573 insertions(+), 18 deletions(-) create mode 100644 pyinjective/client/chain/grpc_stream/__init__.py create mode 100644 pyinjective/client/chain/grpc_stream/chain_grpc_chain_stream.py create mode 100644 tests/client/chain/stream_grpc/__init__.py create mode 100644 tests/client/chain/stream_grpc/configurable_chain_stream_query_servicer.py create mode 100644 tests/client/chain/stream_grpc/test_chain_grpc_chain_stream.py diff --git a/examples/chain_client/49_ChainStream.py b/examples/chain_client/49_ChainStream.py index b4c60213..05a5bbdc 100644 --- a/examples/chain_client/49_ChainStream.py +++ b/examples/chain_client/49_ChainStream.py @@ -1,12 +1,25 @@ import asyncio +from typing import Any, Dict -from google.protobuf import json_format +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.composer import Composer from pyinjective.core.network import Network +async def chain_stream_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to chain stream updates ({exception})") + + +def stream_closed_processor(): + print("The chain stream updates stream has been closed") + + async def main() -> None: network = Network.testnet() @@ -38,24 +51,27 @@ async def main() -> None: subaccount_ids=[subaccount_id], market_ids=[inj_usdt_perp_market] ) oracle_price_filter = composer.chain_stream_oracle_price_filter(symbols=["INJ", "USDT"]) - stream = await client.chain_stream( - bank_balances_filter=bank_balances_filter, - subaccount_deposits_filter=subaccount_deposits_filter, - spot_trades_filter=spot_trades_filter, - derivative_trades_filter=derivative_trades_filter, - spot_orders_filter=spot_orders_filter, - derivative_orders_filter=derivative_orders_filter, - spot_orderbooks_filter=spot_orderbooks_filter, - derivative_orderbooks_filter=derivative_orderbooks_filter, - positions_filter=positions_filter, - oracle_price_filter=oracle_price_filter, - ) - async for event in stream: - print( - json_format.MessageToJson( - message=event, including_default_value_fields=True, preserving_proto_field_name=True - ) + + task = asyncio.get_event_loop().create_task( + client.listen_chain_stream_updates( + callback=chain_stream_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + bank_balances_filter=bank_balances_filter, + subaccount_deposits_filter=subaccount_deposits_filter, + spot_trades_filter=spot_trades_filter, + derivative_trades_filter=derivative_trades_filter, + spot_orders_filter=spot_orders_filter, + derivative_orders_filter=derivative_orders_filter, + spot_orderbooks_filter=spot_orderbooks_filter, + derivative_orderbooks_filter=derivative_orderbooks_filter, + positions_filter=positions_filter, + oracle_price_filter=oracle_price_filter, ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index aa144ce1..184b0ff7 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -12,6 +12,7 @@ from pyinjective.client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi from pyinjective.client.chain.grpc.chain_grpc_authz_api import ChainGrpcAuthZApi from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi +from pyinjective.client.chain.grpc_stream.chain_grpc_chain_stream import ChainGrpcChainStream from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi from pyinjective.client.indexer.grpc.indexer_grpc_auction_api import IndexerGrpcAuctionApi from pyinjective.client.indexer.grpc.indexer_grpc_derivative_api import IndexerGrpcDerivativeApi @@ -181,6 +182,13 @@ def __init__( ), ) + self.chain_stream_api = ChainGrpcChainStream( + channel=self.chain_stream_channel, + metadata_provider=lambda: self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) + self.exchange_account_api = IndexerGrpcAccountApi( channel=self.exchange_channel, metadata_provider=lambda: self.network.exchange_metadata( @@ -2391,6 +2399,10 @@ async def chain_stream( positions_filter: Optional[chain_stream_query.PositionsFilter] = None, oracle_price_filter: Optional[chain_stream_query.OraclePriceFilter] = None, ): + """ + This method is deprecated and will be removed soon. Please use `listen_chain_stream_updates` instead + """ + warn("This method is deprecated. Use listen_chain_stream_updates instead", DeprecationWarning, stacklevel=2) request = chain_stream_query.StreamRequest( bank_balances_filter=bank_balances_filter, subaccount_deposits_filter=subaccount_deposits_filter, @@ -2406,6 +2418,38 @@ async def chain_stream( metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) return self.chain_stream_stub.Stream(request=request, metadata=metadata) + async def listen_chain_stream_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + bank_balances_filter: Optional[chain_stream_query.BankBalancesFilter] = None, + subaccount_deposits_filter: Optional[chain_stream_query.SubaccountDepositsFilter] = None, + spot_trades_filter: Optional[chain_stream_query.TradesFilter] = None, + derivative_trades_filter: Optional[chain_stream_query.TradesFilter] = None, + spot_orders_filter: Optional[chain_stream_query.OrdersFilter] = None, + derivative_orders_filter: Optional[chain_stream_query.OrdersFilter] = None, + spot_orderbooks_filter: Optional[chain_stream_query.OrderbookFilter] = None, + derivative_orderbooks_filter: Optional[chain_stream_query.OrderbookFilter] = None, + positions_filter: Optional[chain_stream_query.PositionsFilter] = None, + oracle_price_filter: Optional[chain_stream_query.OraclePriceFilter] = None, + ): + return await self.chain_stream_api.stream( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + bank_balances_filter=bank_balances_filter, + subaccount_deposits_filter=subaccount_deposits_filter, + spot_trades_filter=spot_trades_filter, + derivative_trades_filter=derivative_trades_filter, + spot_orders_filter=spot_orders_filter, + derivative_orders_filter=derivative_orders_filter, + spot_orderbooks_filter=spot_orderbooks_filter, + derivative_orderbooks_filter=derivative_orderbooks_filter, + positions_filter=positions_filter, + oracle_price_filter=oracle_price_filter, + ) + async def composer(self): return Composer( network=self.network.string(), diff --git a/pyinjective/client/chain/grpc_stream/__init__.py b/pyinjective/client/chain/grpc_stream/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/grpc_stream/chain_grpc_chain_stream.py b/pyinjective/client/chain/grpc_stream/chain_grpc_chain_stream.py new file mode 100644 index 00000000..46b99780 --- /dev/null +++ b/pyinjective/client/chain/grpc_stream/chain_grpc_chain_stream.py @@ -0,0 +1,49 @@ +from typing import Callable, Optional + +from grpc.aio import Channel + +from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_pb, query_pb2_grpc as chain_stream_grpc +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class ChainGrpcChainStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = chain_stream_grpc.StreamStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + bank_balances_filter: Optional[chain_stream_pb.BankBalancesFilter] = None, + subaccount_deposits_filter: Optional[chain_stream_pb.SubaccountDepositsFilter] = None, + spot_trades_filter: Optional[chain_stream_pb.TradesFilter] = None, + derivative_trades_filter: Optional[chain_stream_pb.TradesFilter] = None, + spot_orders_filter: Optional[chain_stream_pb.OrdersFilter] = None, + derivative_orders_filter: Optional[chain_stream_pb.OrdersFilter] = None, + spot_orderbooks_filter: Optional[chain_stream_pb.OrderbookFilter] = None, + derivative_orderbooks_filter: Optional[chain_stream_pb.OrderbookFilter] = None, + positions_filter: Optional[chain_stream_pb.PositionsFilter] = None, + oracle_price_filter: Optional[chain_stream_pb.OraclePriceFilter] = None, + ): + request = chain_stream_pb.StreamRequest( + bank_balances_filter=bank_balances_filter, + subaccount_deposits_filter=subaccount_deposits_filter, + spot_trades_filter=spot_trades_filter, + derivative_trades_filter=derivative_trades_filter, + spot_orders_filter=spot_orders_filter, + derivative_orders_filter=derivative_orders_filter, + spot_orderbooks_filter=spot_orderbooks_filter, + derivative_orderbooks_filter=derivative_orderbooks_filter, + positions_filter=positions_filter, + oracle_price_filter=oracle_price_filter, + ) + + await self._assistant.listen_stream( + call=self._stub.Stream, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/tests/client/chain/stream_grpc/__init__.py b/tests/client/chain/stream_grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/stream_grpc/configurable_chain_stream_query_servicer.py b/tests/client/chain/stream_grpc/configurable_chain_stream_query_servicer.py new file mode 100644 index 00000000..fe27f667 --- /dev/null +++ b/tests/client/chain/stream_grpc/configurable_chain_stream_query_servicer.py @@ -0,0 +1,13 @@ +from collections import deque + +from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_pb, query_pb2_grpc as chain_stream_grpc + + +class ConfigurableChainStreamQueryServicer(chain_stream_grpc.StreamServicer): + def __init__(self): + super().__init__() + self.stream_responses = deque() + + async def Stream(self, request: chain_stream_pb.StreamRequest, context=None, metadata=None): + for event in self.stream_responses: + yield event diff --git a/tests/client/chain/stream_grpc/test_chain_grpc_chain_stream.py b/tests/client/chain/stream_grpc/test_chain_grpc_chain_stream.py new file mode 100644 index 00000000..8d160b14 --- /dev/null +++ b/tests/client/chain/stream_grpc/test_chain_grpc_chain_stream.py @@ -0,0 +1,408 @@ +import asyncio +import base64 + +import grpc +import pytest + +from pyinjective.client.chain.grpc_stream.chain_grpc_chain_stream import ChainGrpcChainStream +from pyinjective.composer import Composer +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.injective.exchange.v1beta1 import exchange_pb2 as exchange_pb +from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_pb +from tests.client.chain.stream_grpc.configurable_chain_stream_query_servicer import ConfigurableChainStreamQueryServicer + + +@pytest.fixture +def chain_stream_servicer(): + return ConfigurableChainStreamQueryServicer() + + +class TestChainGrpcChainStream: + @pytest.mark.asyncio + async def test_stream( + self, + chain_stream_servicer, + ): + block_height = 19114391 + block_time = 1701457189786 + balance_coin = coin_pb.Coin( + denom="inj", + amount="6941221373191000000000", + ) + bank_balance = chain_stream_pb.BankBalance( + account="inj1qaq7mkvuc474k2nyjm2suwyes06fdm4kt26ks4", + balances=[balance_coin], + ) + deposit = exchange_pb.Deposit( + available_balance="112292968420000000000000", + total_balance="73684013968420000000000000", + ) + subaccount_deposit = chain_stream_pb.SubaccountDeposit( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + deposit=deposit, + ) + subaccount_deposits = chain_stream_pb.SubaccountDeposits( + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000007", + deposits=[subaccount_deposit], + ) + spot_trade = chain_stream_pb.SpotTrade( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + is_buy=False, + executionType="LimitMatchNewOrder", + quantity="7000000000000000000000000000000000", + price="18215000", + subaccount_id="0x893f2abf8034627e50cbc63923120b1122503ce0000000000000000000000001", + fee="76503000000000000000", + order_hash=b"\xaa\xb0Ju\xa3)@\xfe\xd58N\xba\xdfG\xfd\xd8}\xe4\r\xf4\xf8a\xd9\n\xa9\xd6x+V\x9b\x02&", + fee_recipient_address="inj13ylj40uqx338u5xtccujxystzy39q08q2gz3dx", + cid="HBOTSIJUT60b77b9c56f0456af96c5c6c0d8", + ) + position_delta = exchange_pb.PositionDelta( + is_long=True, + execution_quantity="5000000", + execution_price="13945600", + execution_margin="69728000", + ) + derivative_trade = chain_stream_pb.DerivativeTrade( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + is_buy=False, + executionType="LimitMatchNewOrder", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + position_delta=position_delta, + payout="0", + fee="76503000000000000000", + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + fee_recipient_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + cid="cid1", + ) + spot_order_info = exchange_pb.OrderInfo( + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + price="18775000", + quantity="54606542000000000000000000000000000000000", + cid="cid2", + ) + spot_limit_order = exchange_pb.SpotLimitOrder( + order_info=spot_order_info, + order_type=exchange_pb.OrderType.SELL_PO, + fillable="54606542000000000000000000000000000000000", + trigger_price="", + order_hash=( + b"\xf9\xc7\xd8v8\x84-\x9b\x99s\xf5\xdfX\xc9\xf9V\x9a\xf7\xf9\xc3\xa1\x00h\t\xc17<\xd1k\x9d\x12\xed" + ), + ) + spot_order = chain_stream_pb.SpotOrder( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + order=spot_limit_order, + ) + spot_order_update = chain_stream_pb.SpotOrderUpdate( + status="Booked", + order_hash=( + b"\xf9\xc7\xd8v8\x84-\x9b\x99s\xf5\xdfX\xc9\xf9V\x9a\xf7\xf9\xc3\xa1\x00h\t\xc17<\xd1k\x9d\x12\xed" + ), + cid="cid2", + order=spot_order, + ) + derivative_order_info = exchange_pb.OrderInfo( + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + price="18775000", + quantity="54606542000000000000000000000000000000000", + cid="cid2", + ) + derivative_limit_order = exchange_pb.DerivativeLimitOrder( + order_info=derivative_order_info, + order_type=exchange_pb.OrderType.SELL_PO, + margin="54606542000000000000000000000000000000000", + fillable="54606542000000000000000000000000000000000", + trigger_price="", + order_hash=b"\x03\xc9\xf8G*Q-G%\xf1\xbcF3\xe89g\xbe\xeag\xd8Y\x7f\x87\x8a\xa5\xac\x8ew\x8a\x91\xa2F", + ) + derivative_order = chain_stream_pb.DerivativeOrder( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + order=derivative_limit_order, + is_market=False, + ) + derivative_order_update = chain_stream_pb.DerivativeOrderUpdate( + status="Booked", + order_hash=b"\x03\xc9\xf8G*Q-G%\xf1\xbcF3\xe89g\xbe\xeag\xd8Y\x7f\x87\x8a\xa5\xac\x8ew\x8a\x91\xa2F", + cid="cid3", + order=derivative_order, + ) + spot_buy_level = exchange_pb.Level(p="17280000", q="44557734000000000000000000000000000000000") + spot_sell_level = exchange_pb.Level( + p="18207000", + q="22196395000000000000000000000000000000000", + ) + spot_orderbook = chain_stream_pb.Orderbook( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + buy_levels=[spot_buy_level], + sell_levels=[spot_sell_level], + ) + spot_orderbook_update = chain_stream_pb.OrderbookUpdate( + seq=6645013, + orderbook=spot_orderbook, + ) + derivative_buy_level = exchange_pb.Level(p="17280000", q="44557734000000000000000000000000000000000") + derivative_sell_level = exchange_pb.Level( + p="18207000", + q="22196395000000000000000000000000000000000", + ) + derivative_orderbook = chain_stream_pb.Orderbook( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + buy_levels=[derivative_buy_level], + sell_levels=[derivative_sell_level], + ) + derivative_orderbook_update = chain_stream_pb.OrderbookUpdate( + seq=6645013, + orderbook=derivative_orderbook, + ) + position = chain_stream_pb.Position( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + isLong=True, + quantity="22196395000000000000000000000000000000000", + entry_price="18207000", + margin="22196395000000000000000000000000000000000", + cumulative_funding_entry="0", + ) + oracle_price = chain_stream_pb.OraclePrice( + symbol="0x41f3625971ca2ed2263e78573fe5ce23e13d2558ed3f2e47ab0f84fb9e7ae722", + price="999910860000000000", + type="pyth", + ) + + chain_stream_servicer.stream_responses.append( + chain_stream_pb.StreamResponse( + block_height=block_height, + block_time=block_time, + bank_balances=[bank_balance], + subaccount_deposits=[subaccount_deposits], + spot_trades=[spot_trade], + derivative_trades=[derivative_trade], + spot_orders=[spot_order_update], + derivative_orders=[derivative_order_update], + spot_orderbook_updates=[spot_orderbook_update], + derivative_orderbook_updates=[derivative_orderbook_update], + positions=[position], + oracle_prices=[oracle_price], + ) + ) + + network = Network.devnet() + composer = Composer(network=network.string()) + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = ChainGrpcChainStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = chain_stream_servicer + + events = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: events.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + bank_balances_filter = composer.chain_stream_bank_balances_filter() + subaccount_deposits_filter = composer.chain_stream_subaccount_deposits_filter() + spot_trades_filter = composer.chain_stream_trades_filter() + derivative_trades_filter = composer.chain_stream_trades_filter() + spot_orders_filter = composer.chain_stream_orders_filter() + derivative_orders_filter = composer.chain_stream_orders_filter() + spot_orderbooks_filter = composer.chain_stream_orderbooks_filter() + derivative_orderbooks_filter = composer.chain_stream_orderbooks_filter() + positions_filter = composer.chain_stream_positions_filter() + oracle_price_filter = composer.chain_stream_oracle_price_filter() + + expected_update = { + "blockHeight": str(block_height), + "blockTime": str(block_time), + "bankBalances": [ + { + "account": bank_balance.account, + "balances": [ + { + "denom": balance_coin.denom, + "amount": balance_coin.amount, + } + ], + }, + ], + "subaccountDeposits": [ + { + "subaccountId": subaccount_deposits.subaccount_id, + "deposits": [ + { + "denom": subaccount_deposit.denom, + "deposit": { + "availableBalance": deposit.available_balance, + "totalBalance": deposit.total_balance, + }, + } + ], + } + ], + "spotTrades": [ + { + "marketId": spot_trade.market_id, + "isBuy": spot_trade.is_buy, + "executionType": spot_trade.executionType, + "quantity": spot_trade.quantity, + "price": spot_trade.price, + "subaccountId": spot_trade.subaccount_id, + "fee": spot_trade.fee, + "orderHash": base64.b64encode(spot_trade.order_hash).decode(), + "feeRecipientAddress": spot_trade.fee_recipient_address, + "cid": spot_trade.cid, + }, + ], + "derivativeTrades": [ + { + "marketId": derivative_trade.market_id, + "isBuy": derivative_trade.is_buy, + "executionType": derivative_trade.executionType, + "subaccountId": derivative_trade.subaccount_id, + "positionDelta": { + "isLong": position_delta.is_long, + "executionMargin": position_delta.execution_margin, + "executionQuantity": position_delta.execution_quantity, + "executionPrice": position_delta.execution_price, + }, + "payout": derivative_trade.payout, + "fee": derivative_trade.fee, + "orderHash": derivative_trade.order_hash, + "feeRecipientAddress": derivative_trade.fee_recipient_address, + "cid": derivative_trade.cid, + } + ], + "spotOrders": [ + { + "status": "Booked", + "orderHash": base64.b64encode(spot_order_update.order_hash).decode(), + "cid": spot_order_update.cid, + "order": { + "marketId": spot_order.market_id, + "order": { + "orderInfo": { + "subaccountId": spot_order_info.subaccount_id, + "feeRecipient": spot_order_info.fee_recipient, + "price": spot_order_info.price, + "quantity": spot_order_info.quantity, + "cid": spot_order_info.cid, + }, + "orderType": "SELL_PO", + "fillable": spot_limit_order.fillable, + "triggerPrice": spot_limit_order.trigger_price, + "orderHash": base64.b64encode(spot_limit_order.order_hash).decode(), + }, + }, + }, + ], + "derivativeOrders": [ + { + "status": "Booked", + "orderHash": base64.b64encode(derivative_order_update.order_hash).decode(), + "cid": derivative_order_update.cid, + "order": { + "marketId": derivative_order.market_id, + "order": { + "orderInfo": { + "subaccountId": derivative_order_info.subaccount_id, + "feeRecipient": derivative_order_info.fee_recipient, + "price": derivative_order_info.price, + "quantity": derivative_order_info.quantity, + "cid": derivative_order_info.cid, + }, + "orderType": "SELL_PO", + "margin": derivative_limit_order.margin, + "fillable": derivative_limit_order.fillable, + "triggerPrice": derivative_limit_order.trigger_price, + "orderHash": base64.b64encode(derivative_limit_order.order_hash).decode(), + }, + "isMarket": derivative_order.is_market, + }, + }, + ], + "spotOrderbookUpdates": [ + { + "seq": str(spot_orderbook_update.seq), + "orderbook": { + "marketId": spot_orderbook.market_id, + "buyLevels": [ + { + "p": spot_buy_level.p, + "q": spot_buy_level.q, + }, + ], + "sellLevels": [ + {"p": spot_sell_level.p, "q": spot_sell_level.q}, + ], + }, + }, + ], + "derivativeOrderbookUpdates": [ + { + "seq": str(derivative_orderbook_update.seq), + "orderbook": { + "marketId": derivative_orderbook.market_id, + "buyLevels": [ + { + "p": derivative_buy_level.p, + "q": derivative_buy_level.q, + }, + ], + "sellLevels": [ + { + "p": derivative_sell_level.p, + "q": derivative_sell_level.q, + }, + ], + }, + }, + ], + "positions": [ + { + "marketId": position.market_id, + "subaccountId": position.subaccount_id, + "isLong": position.isLong, + "quantity": position.quantity, + "entryPrice": position.entry_price, + "margin": position.margin, + "cumulativeFundingEntry": position.cumulative_funding_entry, + } + ], + "oraclePrices": [ + { + "symbol": oracle_price.symbol, + "price": oracle_price.price, + "type": oracle_price.type, + }, + ], + } + + asyncio.get_event_loop().create_task( + api.stream( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + bank_balances_filter=bank_balances_filter, + subaccount_deposits_filter=subaccount_deposits_filter, + spot_trades_filter=spot_trades_filter, + derivative_trades_filter=derivative_trades_filter, + spot_orders_filter=spot_orders_filter, + derivative_orders_filter=derivative_orders_filter, + spot_orderbooks_filter=spot_orderbooks_filter, + derivative_orderbooks_filter=derivative_orderbooks_filter, + positions_filter=positions_filter, + oracle_price_filter=oracle_price_filter, + ) + ) + + first_update = await asyncio.wait_for(events.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index f29db5dd..4a8fa562 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -18,10 +18,12 @@ injective_portfolio_rpc_pb2 as exchange_portfolio_pb, injective_spot_exchange_rpc_pb2 as exchange_spot_pb, ) +from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_pb from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb from tests.client.chain.grpc.configurable_auth_query_servicer import ConfigurableAuthQueryServicer from tests.client.chain.grpc.configurable_autz_query_servicer import ConfigurableAuthZQueryServicer from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer +from tests.client.chain.stream_grpc.configurable_chain_stream_query_servicer import ConfigurableChainStreamQueryServicer from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer from tests.client.indexer.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer from tests.client.indexer.configurable_derivative_query_servicer import ConfigurableDerivativeQueryServicer @@ -59,6 +61,11 @@ def bank_servicer(): return ConfigurableBankQueryServicer() +@pytest.fixture +def chain_stream_servicer(): + return ConfigurableChainStreamQueryServicer() + + @pytest.fixture def derivative_servicer(): return ConfigurableDerivativeQueryServicer() @@ -1605,3 +1612,21 @@ async def test_stream_blocks_deprecation_warning( assert len(all_warnings) == 1 assert issubclass(all_warnings[0].category, DeprecationWarning) assert str(all_warnings[0].message) == "This method is deprecated. Use listen_blocks_updates instead" + + @pytest.mark.asyncio + async def test_chain_stream_deprecation_warning( + self, + chain_stream_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.chain_stream_stub = chain_stream_servicer + chain_stream_servicer.stream_responses.append(chain_stream_pb.StreamRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.chain_stream() + + assert len(all_warnings) == 1 + assert issubclass(all_warnings[0].category, DeprecationWarning) + assert str(all_warnings[0].message) == "This method is deprecated. Use listen_chain_stream_updates instead" From 4de1701e50d58b53dec489b0daafde3f9840eeb4 Mon Sep 17 00:00:00 2001 From: abel Date: Mon, 4 Dec 2023 10:26:23 -0300 Subject: [PATCH 25/27] (fix) Fixed failing deprecation warning tests --- .../test_async_client_deprecation_warnings.py | 558 ++++++++++-------- 1 file changed, 312 insertions(+), 246 deletions(-) diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py index 4a8fa562..13ced627 100644 --- a/tests/test_async_client_deprecation_warnings.py +++ b/tests/test_async_client_deprecation_warnings.py @@ -117,10 +117,11 @@ def test_insecure_parameter_deprecation_warning( insecure=False, ) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) == "insecure parameter in AsyncClient is no longer used and will be deprecated" + str(deprecation_warnings[0].message) + == "insecure parameter in AsyncClient is no longer used and will be deprecated" ) @pytest.mark.asyncio @@ -137,9 +138,9 @@ async def test_get_account_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_account(address="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_account instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_account instead" @pytest.mark.asyncio async def test_get_bank_balance_deprecation_warning( @@ -155,9 +156,9 @@ async def test_get_bank_balance_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_bank_balance(address="", denom="inj") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_bank_balance instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_bank_balance instead" @pytest.mark.asyncio async def test_get_bank_balances_deprecation_warning( @@ -173,9 +174,9 @@ async def test_get_bank_balances_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_bank_balances(address="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_bank_balances instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_bank_balances instead" @pytest.mark.asyncio async def test_get_order_states_deprecation_warning( @@ -191,9 +192,9 @@ async def test_get_order_states_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_order_states(spot_order_hashes=["hash1"], derivative_order_hashes=["hash2"]) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_order_states instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_order_states instead" @pytest.mark.asyncio async def test_get_subaccount_list_deprecation_warning( @@ -209,9 +210,9 @@ async def test_get_subaccount_list_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_subaccount_list(account_address="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_subaccounts_list instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_subaccounts_list instead" @pytest.mark.asyncio async def test_get_subaccount_balances_list_deprecation_warning( @@ -229,9 +230,12 @@ async def test_get_subaccount_balances_list_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_subaccount_balances_list(subaccount_id="", denoms=[]) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_balances_list instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_subaccount_balances_list instead" + ) @pytest.mark.asyncio async def test_get_subaccount_balance_deprecation_warning( @@ -247,9 +251,9 @@ async def test_get_subaccount_balance_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_subaccount_balance(subaccount_id="", denom="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_balance instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_balance instead" @pytest.mark.asyncio async def test_get_subaccount_history_deprecation_warning( @@ -265,9 +269,9 @@ async def test_get_subaccount_history_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_subaccount_history(subaccount_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_history instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_history instead" @pytest.mark.asyncio async def test_get_subaccount_order_summary_deprecation_warning( @@ -285,9 +289,12 @@ async def test_get_subaccount_order_summary_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_subaccount_order_summary(subaccount_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_order_summary instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_subaccount_order_summary instead" + ) @pytest.mark.asyncio async def test_get_portfolio_deprecation_warning( @@ -303,9 +310,9 @@ async def test_get_portfolio_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_portfolio(account_address="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_portfolio instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_portfolio instead" @pytest.mark.asyncio async def test_get_rewards_deprecation_warning( @@ -321,9 +328,9 @@ async def test_get_rewards_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_rewards(account_address="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_rewards instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_rewards instead" @pytest.mark.asyncio async def test_stream_subaccount_balance_deprecation_warning( @@ -341,10 +348,11 @@ async def test_stream_subaccount_balance_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_subaccount_balance(subaccount_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) == "This method is deprecated. Use listen_subaccount_balance_updates instead" + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_subaccount_balance_updates instead" ) @pytest.mark.asyncio @@ -361,9 +369,9 @@ async def test_get_grants_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_grants(granter="granter", grantee="grantee") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_grants instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_grants instead" @pytest.mark.asyncio async def test_simulate_deprecation_warning( @@ -379,9 +387,9 @@ async def test_simulate_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.simulate_tx(tx_byte="".encode()) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use simulate instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use simulate instead" @pytest.mark.asyncio async def test_get_tx_deprecation_warning( @@ -397,9 +405,9 @@ async def test_get_tx_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_tx(tx_hash="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_tx instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_tx instead" @pytest.mark.asyncio async def test_send_tx_sync_mode_deprecation_warning( @@ -415,9 +423,9 @@ async def test_send_tx_sync_mode_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.send_tx_sync_mode(tx_byte="".encode()) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use broadcast_tx_sync_mode instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use broadcast_tx_sync_mode instead" @pytest.mark.asyncio async def test_send_tx_async_mode_deprecation_warning( @@ -433,9 +441,9 @@ async def test_send_tx_async_mode_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.send_tx_async_mode(tx_byte="".encode()) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use broadcast_tx_async_mode instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use broadcast_tx_async_mode instead" @pytest.mark.asyncio async def test_send_tx_block_mode_deprecation_warning( @@ -451,9 +459,11 @@ async def test_send_tx_block_mode_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.send_tx_block_mode(tx_byte="".encode()) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. BLOCK broadcast mode should not be used" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. BLOCK broadcast mode should not be used" + ) @pytest.mark.asyncio async def test_ping_deprecation_warning( @@ -469,9 +479,9 @@ async def test_ping_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.ping() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_ping instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_ping instead" @pytest.mark.asyncio async def test_version_deprecation_warning( @@ -487,9 +497,9 @@ async def test_version_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.version() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_version instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_version instead" @pytest.mark.asyncio async def test_info_deprecation_warning( @@ -505,9 +515,9 @@ async def test_info_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.info() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_info instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_info instead" @pytest.mark.asyncio async def test_stream_keepalive_deprecation_warning( @@ -523,9 +533,9 @@ async def test_stream_keepalive_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_keepalive() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_keepalive instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_keepalive instead" @pytest.mark.asyncio async def test_oracle_list_deprecation_warning( @@ -541,9 +551,9 @@ async def test_oracle_list_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_oracle_list() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_oracle_list instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_oracle_list instead" @pytest.mark.asyncio async def test_get_oracle_list_deprecation_warning( @@ -559,9 +569,9 @@ async def test_get_oracle_list_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_oracle_list() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_oracle_list instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_oracle_list instead" @pytest.mark.asyncio async def test_get_oracle_prices_deprecation_warning( @@ -582,9 +592,9 @@ async def test_get_oracle_prices_deprecation_warning( oracle_scale_factor=6, ) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_oracle_price instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_oracle_price instead" @pytest.mark.asyncio async def test_stream_keepalive_deprecation_warning( @@ -604,9 +614,12 @@ async def test_stream_keepalive_deprecation_warning( oracle_type="pricefeed", ) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_oracle_prices_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_oracle_prices_updates instead" + ) @pytest.mark.asyncio async def test_get_insurance_funds_deprecation_warning( @@ -622,9 +635,9 @@ async def test_get_insurance_funds_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_insurance_funds() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_insurance_funds instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_insurance_funds instead" @pytest.mark.asyncio async def test_get_redemptions_deprecation_warning( @@ -640,9 +653,9 @@ async def test_get_redemptions_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_redemptions() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_redemptions instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_redemptions instead" @pytest.mark.asyncio async def test_get_auction_deprecation_warning( @@ -658,9 +671,9 @@ async def test_get_auction_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_auction(bid_round=1) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_auction instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_auction instead" @pytest.mark.asyncio async def test_get_auctions_deprecation_warning( @@ -676,9 +689,9 @@ async def test_get_auctions_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_auctions() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_auctions instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_auctions instead" @pytest.mark.asyncio async def test_stream_bids_deprecation_warning( @@ -694,9 +707,9 @@ async def test_stream_bids_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_bids() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_bids_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_bids_updates instead" @pytest.mark.asyncio async def test_get_spot_markets_deprecation_warning( @@ -712,9 +725,9 @@ async def test_get_spot_markets_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_spot_markets() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_markets instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_markets instead" @pytest.mark.asyncio async def test_get_spot_market_deprecation_warning( @@ -730,9 +743,9 @@ async def test_get_spot_market_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_spot_market(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_market instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_market instead" @pytest.mark.asyncio async def test_get_spot_orderbookV2_deprecation_warning( @@ -748,9 +761,9 @@ async def test_get_spot_orderbookV2_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_spot_orderbookV2(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_orderbook_v2 instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_orderbook_v2 instead" @pytest.mark.asyncio async def test_get_spot_orderbooksV2_deprecation_warning( @@ -766,9 +779,9 @@ async def test_get_spot_orderbooksV2_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_spot_orderbooksV2(market_ids=[]) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_orderbooks_v2 instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_orderbooks_v2 instead" @pytest.mark.asyncio async def test_get_spot_orders_deprecation_warning( @@ -784,9 +797,9 @@ async def test_get_spot_orders_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_spot_orders(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_orders instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_orders instead" @pytest.mark.asyncio async def test_get_spot_trades_deprecation_warning( @@ -802,9 +815,9 @@ async def test_get_spot_trades_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_spot_trades() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_trades instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_trades instead" @pytest.mark.asyncio async def test_get_spot_subaccount_orders_deprecation_warning( @@ -820,10 +833,11 @@ async def test_get_spot_subaccount_orders_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_spot_subaccount_orders(subaccount_id="", market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_subaccount_orders_list instead" + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_spot_subaccount_orders_list instead" ) @pytest.mark.asyncio @@ -840,10 +854,11 @@ async def test_get_spot_subaccount_trades_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_spot_subaccount_trades(subaccount_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_subaccount_trades_list instead" + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_spot_subaccount_trades_list instead" ) @pytest.mark.asyncio @@ -860,9 +875,11 @@ async def test_get_historical_spot_orders_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_historical_spot_orders() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_spot_orders_history instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_orders_history instead" + ) @pytest.mark.asyncio async def test_stream_spot_markets_deprecation_warning( @@ -878,9 +895,11 @@ async def test_stream_spot_markets_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_spot_markets() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_markets_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_spot_markets_updates instead" + ) @pytest.mark.asyncio async def test_stream_spot_orderbook_snapshot_deprecation_warning( @@ -896,9 +915,12 @@ async def test_stream_spot_orderbook_snapshot_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_spot_orderbook_snapshot(market_ids=[]) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_orderbook_snapshots instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_spot_orderbook_snapshots instead" + ) @pytest.mark.asyncio async def test_stream_spot_orderbook_update_deprecation_warning( @@ -914,9 +936,12 @@ async def test_stream_spot_orderbook_update_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_spot_orderbook_update(market_ids=[]) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_orderbook_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_spot_orderbook_updates instead" + ) @pytest.mark.asyncio async def test_stream_spot_orders_deprecation_warning( @@ -932,9 +957,11 @@ async def test_stream_spot_orders_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_spot_orders(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_orders_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_spot_orders_updates instead" + ) @pytest.mark.asyncio async def test_stream_spot_trades_deprecation_warning( @@ -950,9 +977,11 @@ async def test_stream_spot_trades_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_spot_trades() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_trades_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_spot_trades_updates instead" + ) @pytest.mark.asyncio async def test_stream_historical_spot_orders_deprecation_warning( @@ -968,10 +997,11 @@ async def test_stream_historical_spot_orders_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_historical_spot_orders(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) == "This method is deprecated. Use listen_spot_orders_history_updates instead" + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_spot_orders_history_updates instead" ) @pytest.mark.asyncio @@ -988,9 +1018,9 @@ async def test_get_derivative_markets_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_derivative_markets() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_markets instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_markets instead" @pytest.mark.asyncio async def test_get_derivative_market_deprecation_warning( @@ -1006,9 +1036,9 @@ async def test_get_derivative_market_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_derivative_market(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_market instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_market instead" @pytest.mark.asyncio async def test_get_binary_options_markets_deprecation_warning( @@ -1026,9 +1056,12 @@ async def test_get_binary_options_markets_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_binary_options_markets() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_binary_options_markets instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_binary_options_markets instead" + ) @pytest.mark.asyncio async def test_get_binary_options_market_deprecation_warning( @@ -1044,9 +1077,11 @@ async def test_get_binary_options_market_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_binary_options_market(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_binary_options_market instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_binary_options_market instead" + ) @pytest.mark.asyncio async def test_get_derivative_orderbook_deprecation_warning( @@ -1062,9 +1097,12 @@ async def test_get_derivative_orderbook_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_derivative_orderbook(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orderbook_v2 instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_orderbook_v2 instead" + ) @pytest.mark.asyncio async def test_get_derivative_orderbooksV2_deprecation_warning( @@ -1080,9 +1118,12 @@ async def test_get_derivative_orderbooksV2_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_derivative_orderbooksV2(market_ids=[]) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orderbooks_v2 instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_orderbooks_v2 instead" + ) @pytest.mark.asyncio async def test_get_derivative_orders_deprecation_warning( @@ -1098,9 +1139,9 @@ async def test_get_derivative_orders_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_derivative_orders(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orders instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orders instead" @pytest.mark.asyncio async def test_get_derivative_positions_deprecation_warning( @@ -1116,9 +1157,11 @@ async def test_get_derivative_positions_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_derivative_positions() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_positions instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_positions instead" + ) @pytest.mark.asyncio async def test_get_derivative_liquidable_positions_deprecation_warning( @@ -1134,10 +1177,10 @@ async def test_get_derivative_liquidable_positions_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_derivative_liquidable_positions() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) + str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_liquidable_positions instead" ) @@ -1155,9 +1198,9 @@ async def test_get_funding_payments_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_funding_payments(subaccount_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_funding_payments instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_funding_payments instead" @pytest.mark.asyncio async def test_get_funding_rates_deprecation_warning( @@ -1173,9 +1216,9 @@ async def test_get_funding_rates_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_funding_rates(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_funding_rates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_funding_rates instead" @pytest.mark.asyncio async def test_get_derivative_trades_deprecation_warning( @@ -1191,9 +1234,9 @@ async def test_get_derivative_trades_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_derivative_trades() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_trades instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_trades instead" @pytest.mark.asyncio async def test_get_derivative_subaccount_orders_deprecation_warning( @@ -1211,10 +1254,11 @@ async def test_get_derivative_subaccount_orders_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_derivative_subaccount_orders(subaccount_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_subaccount_orders instead" + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_subaccount_orders instead" ) @pytest.mark.asyncio @@ -1233,10 +1277,11 @@ async def test_get_derivative_subaccount_trades_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_derivative_subaccount_trades(subaccount_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_subaccount_trades instead" + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_subaccount_trades instead" ) @pytest.mark.asyncio @@ -1253,9 +1298,12 @@ async def test_get_historical_derivative_orders_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_historical_derivative_orders() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orders_history instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_orders_history instead" + ) @pytest.mark.asyncio async def test_stream_derivative_markets_deprecation_warning( @@ -1271,9 +1319,12 @@ async def test_stream_derivative_markets_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_derivative_markets() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_market_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_market_updates instead" + ) @pytest.mark.asyncio async def test_stream_derivative_orderbook_snapshot_deprecation_warning( @@ -1289,10 +1340,10 @@ async def test_stream_derivative_orderbook_snapshot_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_derivative_orderbook_snapshot(market_ids=[]) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) + str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_derivative_orderbook_snapshots instead" ) @@ -1312,10 +1363,11 @@ async def test_stream_derivative_orderbook_update_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_derivative_orderbook_update(market_ids=[]) - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_orderbook_updates instead" + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_orderbook_updates instead" ) @pytest.mark.asyncio @@ -1332,10 +1384,11 @@ async def test_stream_derivative_positions_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_derivative_positions() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_positions_updates instead" + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_positions_updates instead" ) @pytest.mark.asyncio @@ -1352,9 +1405,12 @@ async def test_stream_derivative_orders_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_derivative_orders(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_orders_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_orders_updates instead" + ) @pytest.mark.asyncio async def test_stream_derivative_trades_deprecation_warning( @@ -1370,9 +1426,12 @@ async def test_stream_derivative_trades_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_derivative_trades() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_derivative_trades_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_trades_updates instead" + ) @pytest.mark.asyncio async def test_stream_historical_derivative_orders_deprecation_warning( @@ -1388,10 +1447,10 @@ async def test_stream_historical_derivative_orders_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_historical_derivative_orders(market_id="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 assert ( - str(all_warnings[0].message) + str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_derivative_orders_history_updates instead" ) @@ -1409,9 +1468,9 @@ async def test_get_account_portfolio_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_account_portfolio(account_address="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_account_portfolio instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_account_portfolio instead" @pytest.mark.asyncio async def test_stream_account_portfolio_deprecation_warning( @@ -1429,9 +1488,12 @@ async def test_stream_account_portfolio_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_account_portfolio(account_address="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_account_portfolio_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_account_portfolio_updates instead" + ) @pytest.mark.asyncio async def test_get_account_txs_deprecation_warning( @@ -1447,9 +1509,9 @@ async def test_get_account_txs_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_account_txs(address="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_account_txs instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_account_txs instead" @pytest.mark.asyncio async def test_get_blocks_deprecation_warning( @@ -1465,9 +1527,9 @@ async def test_get_blocks_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_blocks() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_blocks instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_blocks instead" @pytest.mark.asyncio async def test_get_block_deprecation_warning( @@ -1483,9 +1545,9 @@ async def test_get_block_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_block(block_height="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_block instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_block instead" @pytest.mark.asyncio async def test_get_txs_deprecation_warning( @@ -1501,9 +1563,9 @@ async def test_get_txs_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_txs() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_txs instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_txs instead" @pytest.mark.asyncio async def test_get_tx_by_hash_deprecation_warning( @@ -1519,9 +1581,9 @@ async def test_get_tx_by_hash_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_tx_by_hash(tx_hash="") - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_tx_by_tx_hash instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_tx_by_tx_hash instead" @pytest.mark.asyncio async def test_get_peggy_deposits_deprecation_warning( @@ -1537,9 +1599,9 @@ async def test_get_peggy_deposits_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_peggy_deposits() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_peggy_deposit_txs instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_peggy_deposit_txs instead" @pytest.mark.asyncio async def test_get_peggy_withdrawals_deprecation_warning( @@ -1555,9 +1617,11 @@ async def test_get_peggy_withdrawals_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_peggy_withdrawals() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_peggy_withdrawal_txs instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_peggy_withdrawal_txs instead" + ) @pytest.mark.asyncio async def test_get_ibc_transfers_deprecation_warning( @@ -1573,9 +1637,9 @@ async def test_get_ibc_transfers_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.get_ibc_transfers() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use fetch_ibc_transfer_txs instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_ibc_transfer_txs instead" @pytest.mark.asyncio async def test_stream_txs_deprecation_warning( @@ -1591,9 +1655,9 @@ async def test_stream_txs_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_txs() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_txs_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_txs_updates instead" @pytest.mark.asyncio async def test_stream_blocks_deprecation_warning( @@ -1609,9 +1673,9 @@ async def test_stream_blocks_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.stream_blocks() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_blocks_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_blocks_updates instead" @pytest.mark.asyncio async def test_chain_stream_deprecation_warning( @@ -1627,6 +1691,8 @@ async def test_chain_stream_deprecation_warning( with catch_warnings(record=True) as all_warnings: await client.chain_stream() - assert len(all_warnings) == 1 - assert issubclass(all_warnings[0].category, DeprecationWarning) - assert str(all_warnings[0].message) == "This method is deprecated. Use listen_chain_stream_updates instead" + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_chain_stream_updates instead" + ) From e72eedf5a438c8169fdec627ad8288e3df6db0d6 Mon Sep 17 00:00:00 2001 From: abel Date: Mon, 4 Dec 2023 10:30:20 -0300 Subject: [PATCH 26/27] (fix) Updated coverage requirement from 50% to 65% --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e58d7f34..8ad2fe1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ omit = ["pyinjective/proto/*"] [tool.coverage.report] skip_empty = true -fail_under = 50 +fail_under = 65 precision = 2 [tool.pytest.ini_options] From 7672c35e04c6f38a50a38aed722616957dcf98d1 Mon Sep 17 00:00:00 2001 From: abel Date: Mon, 4 Dec 2023 16:02:55 -0300 Subject: [PATCH 27/27] (fix) Fixed scripts still referencing deprecated functions in AsyncClient --- .../accounts_rpc/4_SubaccountBalancesList.py | 2 +- .../accounts_rpc/5_SubaccountHistory.py | 7 ++++++- .../exchange_client/meta_rpc/4_StreamKeepAlive.py | 11 ++++++++--- pyinjective/async_client.py | 11 +++++------ .../client/indexer/grpc/indexer_grpc_account_api.py | 12 ++++++------ pyinjective/core/broadcaster.py | 4 ++-- .../indexer/grpc/test_indexer_grpc_account_api.py | 11 ++++++----- 7 files changed, 34 insertions(+), 24 deletions(-) diff --git a/examples/exchange_client/accounts_rpc/4_SubaccountBalancesList.py b/examples/exchange_client/accounts_rpc/4_SubaccountBalancesList.py index 501d327e..7df3be17 100644 --- a/examples/exchange_client/accounts_rpc/4_SubaccountBalancesList.py +++ b/examples/exchange_client/accounts_rpc/4_SubaccountBalancesList.py @@ -9,7 +9,7 @@ async def main() -> None: client = AsyncClient(network) subaccount = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000" denoms = ["inj", "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"] - subacc_balances_list = await client.get_subaccount_balances_list(subaccount_id=subaccount, denoms=denoms) + subacc_balances_list = await client.fetch_subaccount_balances_list(subaccount_id=subaccount, denoms=denoms) print(subacc_balances_list) diff --git a/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py b/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py index 610b0723..a71951cf 100644 --- a/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py +++ b/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -13,8 +14,12 @@ async def main() -> None: skip = 1 limit = 15 end_time = 1665118340224 + pagination = PaginationOption(skip=skip, limit=limit, end_time=end_time) subacc_history = await client.fetch_subaccount_history( - subaccount_id=subaccount, denom=denom, transfer_types=transfer_types, skip=skip, limit=limit, end_time=end_time + subaccount_id=subaccount, + denom=denom, + transfer_types=transfer_types, + pagination=pagination, ) print(subacc_history) diff --git a/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py b/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py index 7724f46e..84a22eb8 100644 --- a/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py +++ b/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py @@ -44,9 +44,14 @@ async def keepalive_event_processor(event: Dict[str, Any]): async def get_markets(client): - stream = await client.stream_spot_markets() - async for market in stream: - print(market) + async def print_market_updates(event: Dict[str, Any]): + print(event) + + await client.listen_spot_markets_updates( + callback=print_market_updates, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) if __name__ == "__main__": diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 9c4c2080..388e7457 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -458,6 +458,9 @@ async def send_tx_async_mode(self, tx_byte: bytes) -> abci_type.TxResponse: result = await self.stubTx.BroadcastTx(request=req, metadata=metadata) return result.tx_response + async def broadcast_tx_async_mode(self, tx_bytes: bytes) -> Dict[str, Any]: + return await self.tx_api.broadcast(tx_bytes=tx_bytes, mode=tx_service.BroadcastMode.BROADCAST_MODE_ASYNC) + async def send_tx_block_mode(self, tx_byte: bytes) -> abci_type.TxResponse: """ This method is deprecated and will be removed soon. BLOCK broadcast mode should not be used @@ -960,17 +963,13 @@ async def fetch_subaccount_history( subaccount_id: str, denom: Optional[str] = None, transfer_types: Optional[List[str]] = None, - skip: Optional[int] = None, - limit: Optional[int] = None, - end_time: Optional[int] = None, + pagination: Optional[PaginationOption] = None, ) -> Dict[str, Any]: return await self.exchange_account_api.fetch_subaccount_history( subaccount_id=subaccount_id, denom=denom, transfer_types=transfer_types, - skip=skip, - limit=limit, - end_time=end_time, + pagination=pagination, ) async def get_subaccount_order_summary(self, subaccount_id: str, **kwargs): diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py index e0296b87..8fc387c1 100644 --- a/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py +++ b/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py @@ -2,6 +2,7 @@ from grpc.aio import Channel +from pyinjective.client.model.pagination import PaginationOption from pyinjective.proto.exchange import ( injective_accounts_rpc_pb2 as exchange_accounts_pb, injective_accounts_rpc_pb2_grpc as exchange_accounts_grpc, @@ -66,17 +67,16 @@ async def fetch_subaccount_history( subaccount_id: str, denom: Optional[str] = None, transfer_types: Optional[List[str]] = None, - skip: Optional[int] = None, - limit: Optional[int] = None, - end_time: Optional[int] = None, + pagination: Optional[PaginationOption] = None, ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() request = exchange_accounts_pb.SubaccountHistoryRequest( subaccount_id=subaccount_id, denom=denom, transfer_types=transfer_types, - skip=skip, - limit=limit, - end_time=end_time, + skip=pagination.skip, + limit=pagination.limit, + end_time=pagination.end_time, ) response = await self._execute_call(call=self._stub.SubaccountHistory, request=request) diff --git a/pyinjective/core/broadcaster.py b/pyinjective/core/broadcaster.py index 379afdaa..45ca0fca 100644 --- a/pyinjective/core/broadcaster.py +++ b/pyinjective/core/broadcaster.py @@ -256,11 +256,11 @@ async def configure_gas_fee_for_transaction( # simulate tx try: - sim_res = await self._client.simulate_tx(sim_tx_raw_bytes) + sim_res = await self._client.simulate(sim_tx_raw_bytes) except RpcError as ex: raise RuntimeError(f"Transaction simulation error: {ex}") - gas_limit = math.ceil(Decimal(str(sim_res.gas_info.gas_used)) * self._gas_limit_adjustment_multiplier) + gas_limit = math.ceil(Decimal(str(sim_res["gasInfo"]["gasUsed"])) * self._gas_limit_adjustment_multiplier) fee = [ self._composer.Coin( diff --git a/tests/client/indexer/grpc/test_indexer_grpc_account_api.py b/tests/client/indexer/grpc/test_indexer_grpc_account_api.py index 3d076caf..8466c29b 100644 --- a/tests/client/indexer/grpc/test_indexer_grpc_account_api.py +++ b/tests/client/indexer/grpc/test_indexer_grpc_account_api.py @@ -1,9 +1,8 @@ -import time - import grpc import pytest from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network from pyinjective.proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_pb from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer @@ -256,9 +255,11 @@ async def test_subaccount_history( subaccount_id=transfer.dst_subaccount_id, denom=transfer.amount.denom, transfer_types=[transfer.transfer_type], - skip=0, - limit=5, - end_time=int(time.time() * 1e3), + pagination=PaginationOption( + skip=0, + limit=100, + end_time=1699744939364, + ), ) expected_subaccount_history = { "transfers": [