From f72b7dc963c5283b264254b38fa4dc86071934e9 Mon Sep 17 00:00:00 2001 From: abel Date: Tue, 2 Jan 2024 15:35:21 -0300 Subject: [PATCH 1/7] (feat) Added support for missing bank module endpoints --- examples/chain_client/58_ContractInfo.py | 16 + examples/chain_client/59_ContractHistory.py | 20 + examples/chain_client/60_ContractsByCode.py | 20 + examples/chain_client/61_AllContractsState.py | 20 + examples/chain_client/62_RawContractState.py | 17 + .../chain_client/63_SmartContractState.py | 17 + examples/chain_client/64_SmartContractCode.py | 20 + .../chain_client/65_SmartContractCodes.py | 19 + .../66_SmartContractPinnedCodes.py | 19 + .../chain_client/67_ContractsByCreator.py | 20 + pyinjective/async_client.py | 76 +++ .../client/chain/grpc/chain_grpc_wasm_api.py | 144 +++++ .../grpc/configurable_wasm_query_servicer.py | 56 ++ .../chain/grpc/test_chain_grpc_wasm_api.py | 508 ++++++++++++++++++ 14 files changed, 972 insertions(+) create mode 100644 examples/chain_client/58_ContractInfo.py create mode 100644 examples/chain_client/59_ContractHistory.py create mode 100644 examples/chain_client/60_ContractsByCode.py create mode 100644 examples/chain_client/61_AllContractsState.py create mode 100644 examples/chain_client/62_RawContractState.py create mode 100644 examples/chain_client/63_SmartContractState.py create mode 100644 examples/chain_client/64_SmartContractCode.py create mode 100644 examples/chain_client/65_SmartContractCodes.py create mode 100644 examples/chain_client/66_SmartContractPinnedCodes.py create mode 100644 examples/chain_client/67_ContractsByCreator.py create mode 100644 pyinjective/client/chain/grpc/chain_grpc_wasm_api.py create mode 100644 tests/client/chain/grpc/configurable_wasm_query_servicer.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_wasm_api.py diff --git a/examples/chain_client/58_ContractInfo.py b/examples/chain_client/58_ContractInfo.py new file mode 100644 index 00000000..9cee904c --- /dev/null +++ b/examples/chain_client/58_ContractInfo.py @@ -0,0 +1,16 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + address = "inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7" + contract_info = await client.fetch_contract_info(address=address) + print(contract_info) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/59_ContractHistory.py b/examples/chain_client/59_ContractHistory.py new file mode 100644 index 00000000..460056a8 --- /dev/null +++ b/examples/chain_client/59_ContractHistory.py @@ -0,0 +1,20 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + address = "inj18pp4vjwucpgg4nw3rr4wh4zyjg9ct5t8v9wqgj" + limit = 2 + pagination = PaginationOption(limit=limit) + contract_history = await client.fetch_contract_history(address=address, pagination=pagination) + print(contract_history) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/60_ContractsByCode.py b/examples/chain_client/60_ContractsByCode.py new file mode 100644 index 00000000..1994ce87 --- /dev/null +++ b/examples/chain_client/60_ContractsByCode.py @@ -0,0 +1,20 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + code_id = 3770 + limit = 2 + pagination = PaginationOption(limit=limit) + contracts = await client.fetch_contracts_by_code(code_id=code_id, pagination=pagination) + print(contracts) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/61_AllContractsState.py b/examples/chain_client/61_AllContractsState.py new file mode 100644 index 00000000..a96bfe40 --- /dev/null +++ b/examples/chain_client/61_AllContractsState.py @@ -0,0 +1,20 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + address = "inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7" + limit = 2 + pagination = PaginationOption(limit=limit) + contract_history = await client.fetch_all_contracts_state(address=address, pagination=pagination) + print(contract_history) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/62_RawContractState.py b/examples/chain_client/62_RawContractState.py new file mode 100644 index 00000000..5c9bce71 --- /dev/null +++ b/examples/chain_client/62_RawContractState.py @@ -0,0 +1,17 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + address = "inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7" + query_data = '{"get_count": {}}' + contract_state = await client.fetch_raw_contract_state(address=address, query_data=query_data) + print(contract_state) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/63_SmartContractState.py b/examples/chain_client/63_SmartContractState.py new file mode 100644 index 00000000..b416d88c --- /dev/null +++ b/examples/chain_client/63_SmartContractState.py @@ -0,0 +1,17 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + address = "inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7" + query_data = '{"get_count": {}}' + contract_state = await client.fetch_smart_contract_state(address=address, query_data=query_data) + print(contract_state) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/64_SmartContractCode.py b/examples/chain_client/64_SmartContractCode.py new file mode 100644 index 00000000..d523605d --- /dev/null +++ b/examples/chain_client/64_SmartContractCode.py @@ -0,0 +1,20 @@ +import asyncio +import base64 + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + response = await client.fetch_code(code_id=290) + print(response) + + code = base64.b64decode(response["data"]).decode(encoding="utf-8", errors="replace") + + print(f"\n\n\n{code}") + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/65_SmartContractCodes.py b/examples/chain_client/65_SmartContractCodes.py new file mode 100644 index 00000000..96135ba3 --- /dev/null +++ b/examples/chain_client/65_SmartContractCodes.py @@ -0,0 +1,19 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + limit = 2 + pagination = PaginationOption(limit=limit) + response = await client.fetch_codes(pagination=pagination) + print(response) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/66_SmartContractPinnedCodes.py b/examples/chain_client/66_SmartContractPinnedCodes.py new file mode 100644 index 00000000..9d435314 --- /dev/null +++ b/examples/chain_client/66_SmartContractPinnedCodes.py @@ -0,0 +1,19 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + limit = 2 + pagination = PaginationOption(limit=limit) + response = await client.fetch_pinned_codes(pagination=pagination) + print(response) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/67_ContractsByCreator.py b/examples/chain_client/67_ContractsByCreator.py new file mode 100644 index 00000000..2014eee1 --- /dev/null +++ b/examples/chain_client/67_ContractsByCreator.py @@ -0,0 +1,20 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + creator = "inj1h3gepa4tszh66ee67he53jzmprsqc2l9npq3ty" + limit = 2 + pagination = PaginationOption(limit=limit) + response = await client.fetch_contracts_by_creator(creator_address=creator, pagination=pagination) + print(response) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 50eddecf..66892d89 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -13,6 +13,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.chain_grpc_wasm_api import ChainGrpcWasmApi 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 @@ -183,6 +184,12 @@ def __init__( metadata_query_provider=self._chain_cookie_metadata_requestor ), ) + self.wasm_api = ChainGrpcWasmApi( + channel=self.chain_channel, + metadata_provider=lambda: self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) self.chain_stream_api = ChainGrpcChainStream( channel=self.chain_stream_channel, @@ -662,6 +669,75 @@ async def listen_keepalive( on_status_callback=on_status_callback, ) + # Wasm module + async def fetch_contract_info(self, address: str) -> Dict[str, Any]: + return await self.wasm_api.fetch_contract_info(address=address) + + async def fetch_contract_history( + self, + address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_contract_history( + address=address, + pagination=pagination, + ) + + async def fetch_contracts_by_code( + self, + code_id: int, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_contracts_by_code( + code_id=code_id, + pagination=pagination, + ) + + async def fetch_all_contracts_state( + self, + address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_all_contracts_state( + address=address, + pagination=pagination, + ) + + async def fetch_raw_contract_state(self, address: str, query_data: str) -> Dict[str, Any]: + return await self.wasm_api.fetch_raw_contract_state(address=address, query_data=query_data) + + async def fetch_smart_contract_state(self, address: str, query_data: str) -> Dict[str, Any]: + return await self.wasm_api.fetch_smart_contract_state(address=address, query_data=query_data) + + async def fetch_code(self, code_id: int) -> Dict[str, Any]: + return await self.wasm_api.fetch_code(code_id=code_id) + + async def fetch_codes( + self, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_codes( + pagination=pagination, + ) + + async def fetch_pinned_codes( + self, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_pinned_codes( + pagination=pagination, + ) + + async def fetch_contracts_by_creator( + self, + creator_address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_contracts_by_creator( + creator_address=creator_address, + pagination=pagination, + ) + # Explorer RPC async def get_tx_by_hash(self, tx_hash: str): diff --git a/pyinjective/client/chain/grpc/chain_grpc_wasm_api.py b/pyinjective/client/chain/grpc/chain_grpc_wasm_api.py new file mode 100644 index 00000000..1d08e1c3 --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_wasm_api.py @@ -0,0 +1,144 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.cosmwasm.wasm.v1 import query_pb2 as wasm_query_pb, query_pb2_grpc as wasm_query_grpc +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class ChainGrpcWasmApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = wasm_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = wasm_query_pb.QueryParamsRequest() + response = await self._execute_call(call=self._stub.Params, request=request) + + return response + + async def fetch_contract_info(self, address: str) -> Dict[str, Any]: + request = wasm_query_pb.QueryContractInfoRequest(address=address) + response = await self._execute_call(call=self._stub.ContractInfo, request=request) + + return response + + async def fetch_contract_history( + self, + address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryContractHistoryRequest( + address=address, + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.ContractHistory, request=request) + + return response + + async def fetch_contracts_by_code( + self, + code_id: int, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryContractsByCodeRequest( + code_id=code_id, + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.ContractsByCode, request=request) + + return response + + async def fetch_all_contracts_state( + self, + address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryAllContractStateRequest( + address=address, + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.AllContractState, request=request) + + return response + + async def fetch_raw_contract_state(self, address: str, query_data: str) -> Dict[str, Any]: + request = wasm_query_pb.QueryRawContractStateRequest( + address=address, + query_data=query_data.encode(), + ) + response = await self._execute_call(call=self._stub.RawContractState, request=request) + + return response + + async def fetch_smart_contract_state(self, address: str, query_data: str) -> Dict[str, Any]: + request = wasm_query_pb.QuerySmartContractStateRequest( + address=address, + query_data=query_data.encode(), + ) + response = await self._execute_call(call=self._stub.SmartContractState, request=request) + + return response + + async def fetch_code(self, code_id: int) -> Dict[str, Any]: + request = wasm_query_pb.QueryCodeRequest(code_id=code_id) + response = await self._execute_call(call=self._stub.Code, request=request) + + return response + + async def fetch_codes( + self, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryCodesRequest( + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.Codes, request=request) + + return response + + async def fetch_pinned_codes( + self, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryPinnedCodesRequest( + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.PinnedCodes, request=request) + + return response + + async def fetch_contracts_by_creator( + self, + creator_address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryContractsByCreatorRequest( + creator_address=creator_address, + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.ContractsByCreator, 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/configurable_wasm_query_servicer.py b/tests/client/chain/grpc/configurable_wasm_query_servicer.py new file mode 100644 index 00000000..8b087e4c --- /dev/null +++ b/tests/client/chain/grpc/configurable_wasm_query_servicer.py @@ -0,0 +1,56 @@ +from collections import deque + +from pyinjective.proto.cosmwasm.wasm.v1 import query_pb2 as wasm_query_pb, query_pb2_grpc as wasm_query_grpc + + +class ConfigurableWasmQueryServicer(wasm_query_grpc.QueryServicer): + def __init__(self): + super().__init__() + self.params_responses = deque() + self.contract_info_responses = deque() + self.contract_history_responses = deque() + self.contracts_by_code_responses = deque() + self.all_contracts_state_responses = deque() + self.raw_contract_state_responses = deque() + self.smart_contract_state_responses = deque() + self.code_responses = deque() + self.codes_responses = deque() + self.pinned_codes_responses = deque() + self.contracts_by_creator_responses = deque() + + async def Params(self, request: wasm_query_pb.QueryParamsRequest, context=None, metadata=None): + return self.params_responses.pop() + + async def ContractInfo(self, request: wasm_query_pb.QueryContractInfoRequest, context=None, metadata=None): + return self.contract_info_responses.pop() + + async def ContractHistory(self, request: wasm_query_pb.QueryContractHistoryRequest, context=None, metadata=None): + return self.contract_history_responses.pop() + + async def ContractsByCode(self, request: wasm_query_pb.QueryContractsByCodeRequest, context=None, metadata=None): + return self.contracts_by_code_responses.pop() + + async def AllContractState(self, request: wasm_query_pb.QueryAllContractStateRequest, context=None, metadata=None): + return self.all_contracts_state_responses.pop() + + async def RawContractState(self, request: wasm_query_pb.QueryRawContractStateRequest, context=None, metadata=None): + return self.raw_contract_state_responses.pop() + + async def SmartContractState( + self, request: wasm_query_pb.QuerySmartContractStateRequest, context=None, metadata=None + ): + return self.smart_contract_state_responses.pop() + + async def Code(self, request: wasm_query_pb.QueryCodeRequest, context=None, metadata=None): + return self.code_responses.pop() + + async def Codes(self, request: wasm_query_pb.QueryCodesRequest, context=None, metadata=None): + return self.codes_responses.pop() + + async def PinnedCodes(self, request: wasm_query_pb.QueryPinnedCodesRequest, context=None, metadata=None): + return self.pinned_codes_responses.pop() + + async def ContractsByCreator( + self, request: wasm_query_pb.QueryContractsByCreatorRequest, context=None, metadata=None + ): + return self.contracts_by_creator_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_wasm_api.py b/tests/client/chain/grpc/test_chain_grpc_wasm_api.py new file mode 100644 index 00000000..9822d1ee --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_wasm_api.py @@ -0,0 +1,508 @@ +import base64 +import json + +import grpc +import pytest + +from pyinjective.client.chain.grpc.chain_grpc_wasm_api import ChainGrpcWasmApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from pyinjective.proto.cosmwasm.wasm.v1 import query_pb2 as wasm_query_pb, types_pb2 as wasm_types_pb +from tests.client.chain.grpc.configurable_wasm_query_servicer import ConfigurableWasmQueryServicer + + +@pytest.fixture +def wasm_servicer(): + return ConfigurableWasmQueryServicer() + + +class TestChainGrpcBankApi: + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + wasm_servicer, + ): + access_config = wasm_types_pb.AccessConfig( + permission=0, + addresses=["inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7"], + ) + params = wasm_types_pb.Params( + code_upload_access=access_config, + instantiate_default_permission=0, + ) + wasm_servicer.params_responses.append(wasm_query_pb.QueryParamsResponse(params=params)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + module_params = await api.fetch_module_params() + expected_params = { + "params": { + "codeUploadAccess": { + "permission": wasm_types_pb.AccessType.Name(access_config.permission), + "address": "", + "addresses": access_config.addresses, + }, + "instantiateDefaultPermission": wasm_types_pb.AccessType.Name(params.instantiate_default_permission), + } + } + + assert expected_params == module_params + + @pytest.mark.asyncio + async def test_fetch_contract_info( + self, + wasm_servicer, + ): + address = "inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r" + tx_position = wasm_types_pb.AbsoluteTxPosition( + block_height=1234, + tx_index=9999, + ) + contract_info = wasm_types_pb.ContractInfo( + code_id=10, + creator="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + admin="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + label="test label", + created=tx_position, + ibc_port_id="ibc port id", + ) + wasm_servicer.contract_info_responses.append( + wasm_query_pb.QueryContractInfoResponse( + address=address, + contract_info=contract_info, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_contract_info(address=address) + expected_contract_info = { + "address": address, + "contractInfo": { + "codeId": str(contract_info.code_id), + "creator": contract_info.creator, + "admin": contract_info.admin, + "label": contract_info.label, + "created": { + "blockHeight": str(tx_position.block_height), + "txIndex": str(tx_position.tx_index), + }, + "ibcPortId": contract_info.ibc_port_id, + }, + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_contract_history( + self, + wasm_servicer, + ): + tx_position = wasm_types_pb.AbsoluteTxPosition( + block_height=1234, + tx_index=9999, + ) + history_entry = wasm_types_pb.ContractCodeHistoryEntry( + operation=0, + code_id=3770, + updated=tx_position, + msg="raw message test".encode(), + ) + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.contract_history_responses.append( + wasm_query_pb.QueryContractHistoryResponse( + entries=[history_entry], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_contract_history( + address="inj18pp4vjwucpgg4nw3rr4wh4zyjg9ct5t8v9wqgj", + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_contract_info = { + "entries": [ + { + "operation": wasm_types_pb.ContractCodeHistoryOperationType.Name(history_entry.operation), + "codeId": str(history_entry.code_id), + "updated": { + "blockHeight": str(tx_position.block_height), + "txIndex": str(tx_position.tx_index), + }, + "msg": base64.b64encode(history_entry.msg).decode(), + } + ], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_contracts_by_code( + self, + wasm_servicer, + ): + contract = "inj1z4l7jc8dj3y9484aqcrmf6y8mcctvkmm9zkf7n" + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.contracts_by_code_responses.append( + wasm_query_pb.QueryContractsByCodeResponse( + contracts=[contract], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_contracts_by_code( + code_id=3770, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_contract_info = { + "contracts": [contract], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_all_contracts_state( + self, + wasm_servicer, + ): + model = wasm_types_pb.Model( + key=( + "\x00\x08redeemed\x00*inj13t085sclq8fxy8d3gcjt3jap45j4fwlc79lykw" "\x00\x00\x00\x00\x00\x00\x00\x00" + ).encode(), + value='{"phase_id":0,"redeemed":1,"mint_limit":1}'.encode(), + ) + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.all_contracts_state_responses.append( + wasm_query_pb.QueryAllContractStateResponse( + models=[model], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_all_contracts_state( + address="inj1z4l7jc8dj3y9484aqcrmf6y8mcctvkmm9zkf7n", + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_contract_info = { + "models": [{"key": base64.b64encode(model.key).decode(), "value": base64.b64encode(model.value).decode()}], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_raw_contract_state( + self, + wasm_servicer, + ): + data = "test data".encode() + wasm_servicer.raw_contract_state_responses.append( + wasm_query_pb.QueryRawContractStateResponse( + data=data, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_raw_contract_state( + address="inj1z4l7jc8dj3y9484aqcrmf6y8mcctvkmm9zkf7n", + query_data="query data", + ) + expected_contract_info = { + "data": base64.b64encode(data).decode(), + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_smart_contract_state( + self, + wasm_servicer, + ): + data = json.dumps({"count": 1037}).encode() + wasm_servicer.smart_contract_state_responses.append( + wasm_query_pb.QuerySmartContractStateResponse( + data=data, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_smart_contract_state( + address="inj1z4l7jc8dj3y9484aqcrmf6y8mcctvkmm9zkf7n", + query_data="query data", + ) + expected_contract_info = { + "data": base64.b64encode(data).decode(), + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_code( + self, + wasm_servicer, + ): + access_config = wasm_types_pb.AccessConfig( + permission=0, + addresses=["inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7"], + ) + code_info_response = wasm_query_pb.CodeInfoResponse( + code_id=290, + creator="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + data_hash="0xf8e7689e23ac0c9d53f44a8fd98c686c20b0140a8d76d600e2c546bfbba7758d".encode(), + instantiate_permission=access_config, + ) + data = "code".encode() + wasm_servicer.code_responses.append( + wasm_query_pb.QueryCodeResponse( + code_info=code_info_response, + data=data, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_code(code_id=code_info_response.code_id) + expected_contract_info = { + "codeInfo": { + "codeId": str(code_info_response.code_id), + "creator": code_info_response.creator, + "dataHash": base64.b64encode(code_info_response.data_hash).decode(), + "instantiatePermission": { + "permission": wasm_types_pb.AccessType.Name(access_config.permission), + "address": "", + "addresses": access_config.addresses, + }, + }, + "data": base64.b64encode(data).decode(), + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_codes( + self, + wasm_servicer, + ): + access_config = wasm_types_pb.AccessConfig( + permission=0, + addresses=["inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7"], + ) + code_info_response = wasm_query_pb.CodeInfoResponse( + code_id=290, + creator="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + data_hash="0xf8e7689e23ac0c9d53f44a8fd98c686c20b0140a8d76d600e2c546bfbba7758d".encode(), + instantiate_permission=access_config, + ) + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.codes_responses.append( + wasm_query_pb.QueryCodesResponse( + code_infos=[code_info_response], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + codes = await api.fetch_codes( + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_codes = { + "codeInfos": [ + { + "codeId": str(code_info_response.code_id), + "creator": code_info_response.creator, + "dataHash": base64.b64encode(code_info_response.data_hash).decode(), + "instantiatePermission": { + "permission": wasm_types_pb.AccessType.Name(access_config.permission), + "address": "", + "addresses": access_config.addresses, + }, + }, + ], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert codes == expected_codes + + @pytest.mark.asyncio + async def test_fetch_pinned_codes( + self, + wasm_servicer, + ): + code_id = 290 + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.pinned_codes_responses.append( + wasm_query_pb.QueryPinnedCodesResponse( + code_ids=[code_id], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + codes = await api.fetch_pinned_codes( + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_codes = { + "codeIds": [str(code_id)], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert codes == expected_codes + + @pytest.mark.asyncio + async def test_contracts_by_creator( + self, + wasm_servicer, + ): + contract_address = "inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7" + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.contracts_by_creator_responses.append( + wasm_query_pb.QueryContractsByCreatorResponse( + contract_addresses=[contract_address], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + codes = await api.fetch_contracts_by_creator( + creator_address="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_codes = { + "contractAddresses": [contract_address], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert codes == expected_codes + + async def _dummy_metadata_provider(self): + return None From 4f14a69a9bcc3230c2d3eee92461108a47bf48f1 Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 3 Jan 2024 12:36:32 -0300 Subject: [PATCH 2/7] (feat) Implemented all TokenFactory query module endpoints --- .../chain_client/68_AuthorityDenomMetadata.py | 15 ++ examples/chain_client/69_DenomsFromCreator.py | 15 ++ .../70_TokenfactoryModuleState.py | 15 ++ pyinjective/async_client.py | 25 +++ .../grpc/chain_grpc_token_factory_api.py | 51 ++++++ ...nfigurable_token_factory_query_servicer.py | 33 ++++ .../grpc/test_chain_grpc_token_factory_api.py | 159 ++++++++++++++++++ 7 files changed, 313 insertions(+) create mode 100644 examples/chain_client/68_AuthorityDenomMetadata.py create mode 100644 examples/chain_client/69_DenomsFromCreator.py create mode 100644 examples/chain_client/70_TokenfactoryModuleState.py create mode 100644 pyinjective/client/chain/grpc/chain_grpc_token_factory_api.py create mode 100644 tests/client/chain/grpc/configurable_token_factory_query_servicer.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_token_factory_api.py diff --git a/examples/chain_client/68_AuthorityDenomMetadata.py b/examples/chain_client/68_AuthorityDenomMetadata.py new file mode 100644 index 00000000..f5ebaab5 --- /dev/null +++ b/examples/chain_client/68_AuthorityDenomMetadata.py @@ -0,0 +1,15 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + metadata = await client.fetch_denom_authority_metadata(creator="inj1zvy8xrlhe7ex9scer868clfstdv7j6vz790kwa") + print(metadata) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/69_DenomsFromCreator.py b/examples/chain_client/69_DenomsFromCreator.py new file mode 100644 index 00000000..17a486d1 --- /dev/null +++ b/examples/chain_client/69_DenomsFromCreator.py @@ -0,0 +1,15 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + denoms = await client.fetch_denoms_from_creator(creator="inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3") + print(denoms) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/70_TokenfactoryModuleState.py b/examples/chain_client/70_TokenfactoryModuleState.py new file mode 100644 index 00000000..8f9b08f3 --- /dev/null +++ b/examples/chain_client/70_TokenfactoryModuleState.py @@ -0,0 +1,15 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + state = await client.fetch_tokenfactory_module_state() + print(state) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 66892d89..ec42c2ce 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -13,6 +13,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.chain_grpc_token_factory_api import ChainGrpcTokenFactoryApi from pyinjective.client.chain.grpc.chain_grpc_wasm_api import ChainGrpcWasmApi from pyinjective.client.chain.grpc_stream.chain_grpc_chain_stream import ChainGrpcChainStream from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi @@ -178,6 +179,12 @@ def __init__( metadata_query_provider=self._chain_cookie_metadata_requestor ), ) + self.token_factory_api = ChainGrpcTokenFactoryApi( + channel=self.chain_channel, + 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=lambda: self.network.chain_metadata( @@ -738,6 +745,24 @@ async def fetch_contracts_by_creator( pagination=pagination, ) + # Token Factory module + + async def fetch_denom_authority_metadata( + self, + creator: str, + sub_denom: Optional[str] = None, + ) -> Dict[str, Any]: + return await self.token_factory_api.fetch_denom_authority_metadata(creator=creator, sub_denom=sub_denom) + + async def fetch_denoms_from_creator( + self, + creator: str, + ) -> Dict[str, Any]: + return await self.token_factory_api.fetch_denoms_from_creator(creator=creator) + + async def fetch_tokenfactory_module_state(self) -> Dict[str, Any]: + return await self.token_factory_api.fetch_tokenfactory_module_state() + # Explorer RPC async def get_tx_by_hash(self, tx_hash: str): diff --git a/pyinjective/client/chain/grpc/chain_grpc_token_factory_api.py b/pyinjective/client/chain/grpc/chain_grpc_token_factory_api.py new file mode 100644 index 00000000..48e1df1c --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_token_factory_api.py @@ -0,0 +1,51 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.proto.injective.tokenfactory.v1beta1 import ( + query_pb2 as token_factory_query_pb, + query_pb2_grpc as token_factory_query_grpc, + tx_pb2_grpc as token_factory_tx_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class ChainGrpcTokenFactoryApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._query_stub = token_factory_query_grpc.QueryStub(channel) + self._tx_stub = token_factory_tx_grpc.MsgStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = token_factory_query_pb.QueryParamsRequest() + response = await self._execute_call(call=self._query_stub.Params, request=request) + + return response + + async def fetch_denom_authority_metadata( + self, + creator: str, + sub_denom: Optional[str] = None, + ) -> Dict[str, Any]: + request = token_factory_query_pb.QueryDenomAuthorityMetadataRequest( + creator=creator, + sub_denom=sub_denom, + ) + response = await self._execute_call(call=self._query_stub.DenomAuthorityMetadata, request=request) + + return response + + async def fetch_denoms_from_creator(self, creator: str) -> Dict[str, Any]: + request = token_factory_query_pb.QueryDenomsFromCreatorRequest(creator=creator) + response = await self._execute_call(call=self._query_stub.DenomsFromCreator, request=request) + + return response + + async def fetch_tokenfactory_module_state(self) -> Dict[str, Any]: + request = token_factory_query_pb.QueryModuleStateRequest() + response = await self._execute_call(call=self._query_stub.TokenfactoryModuleState, 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/configurable_token_factory_query_servicer.py b/tests/client/chain/grpc/configurable_token_factory_query_servicer.py new file mode 100644 index 00000000..56fb8d58 --- /dev/null +++ b/tests/client/chain/grpc/configurable_token_factory_query_servicer.py @@ -0,0 +1,33 @@ +from collections import deque + +from pyinjective.proto.injective.tokenfactory.v1beta1 import ( + query_pb2 as token_factory_query_pb, + query_pb2_grpc as token_factory_query_grpc, +) + + +class ConfigurableTokenFactoryQueryServicer(token_factory_query_grpc.QueryServicer): + def __init__(self): + super().__init__() + self.params_responses = deque() + self.denom_authority_metadata_responses = deque() + self.denoms_from_creator_responses = deque() + self.tokenfactory_module_state_responses = deque() + + async def Params(self, request: token_factory_query_pb.QueryParamsRequest, context=None, metadata=None): + return self.params_responses.pop() + + async def DenomAuthorityMetadata( + self, request: token_factory_query_pb.QueryDenomAuthorityMetadataRequest, context=None, metadata=None + ): + return self.denom_authority_metadata_responses.pop() + + async def DenomsFromCreator( + self, request: token_factory_query_pb.QueryDenomsFromCreatorRequest, context=None, metadata=None + ): + return self.denoms_from_creator_responses.pop() + + async def TokenfactoryModuleState( + self, request: token_factory_query_pb.QueryModuleStateRequest, context=None, metadata=None + ): + return self.tokenfactory_module_state_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_token_factory_api.py b/tests/client/chain/grpc/test_chain_grpc_token_factory_api.py new file mode 100644 index 00000000..418dfb35 --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_token_factory_api.py @@ -0,0 +1,159 @@ +import grpc +import pytest + +from pyinjective.client.chain.grpc.chain_grpc_token_factory_api import ChainGrpcTokenFactoryApi +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.injective.tokenfactory.v1beta1 import ( + authorityMetadata_pb2 as token_factory_authority_metadata_pb, + genesis_pb2 as token_factory_genesis_pb, + params_pb2 as token_factory_params_pb, + query_pb2 as token_factory_query_pb, +) +from tests.client.chain.grpc.configurable_token_factory_query_servicer import ConfigurableTokenFactoryQueryServicer + + +@pytest.fixture +def token_factory_query_servicer(): + return ConfigurableTokenFactoryQueryServicer() + + +class TestChainGrpcTokenFactoryApi: + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + token_factory_query_servicer, + ): + coin = coin_pb.Coin(denom="inj", amount="988987297011197594664") + params = token_factory_params_pb.Params( + denom_creation_fee=[coin], + ) + token_factory_query_servicer.params_responses.append(token_factory_query_pb.QueryParamsResponse(params=params)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcTokenFactoryApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._query_stub = token_factory_query_servicer + + module_params = await api.fetch_module_params() + expected_params = { + "params": { + "denomCreationFee": [ + {"denom": coin.denom, "amount": coin.amount}, + ], + } + } + + assert module_params == expected_params + + @pytest.mark.asyncio + async def test_fetch_denom_authority_metadata( + self, + token_factory_query_servicer, + ): + authority_metadata = token_factory_authority_metadata_pb.DenomAuthorityMetadata( + admin="inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7", + ) + token_factory_query_servicer.denom_authority_metadata_responses.append( + token_factory_query_pb.QueryDenomAuthorityMetadataResponse( + authority_metadata=authority_metadata, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcTokenFactoryApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._query_stub = token_factory_query_servicer + + metadata = await api.fetch_denom_authority_metadata( + creator=authority_metadata.admin, + sub_denom="inj", + ) + expected_metadata = { + "authorityMetadata": { + "admin": authority_metadata.admin, + }, + } + + assert metadata == expected_metadata + + @pytest.mark.asyncio + async def test_fetch_denoms_from_creator( + self, + token_factory_query_servicer, + ): + denom = "factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth" + token_factory_query_servicer.denoms_from_creator_responses.append( + token_factory_query_pb.QueryDenomsFromCreatorResponse(denoms=[denom]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcTokenFactoryApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._query_stub = token_factory_query_servicer + + denoms = await api.fetch_denoms_from_creator(creator="inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7") + expected_denoms = {"denoms": [denom]} + + assert denoms == expected_denoms + + @pytest.mark.asyncio + async def test_fetch_tokenfactory_module_state( + self, + token_factory_query_servicer, + ): + coin = coin_pb.Coin(denom="inj", amount="988987297011197594664") + params = token_factory_params_pb.Params( + denom_creation_fee=[coin], + ) + authority_metadata = token_factory_authority_metadata_pb.DenomAuthorityMetadata( + admin="inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0", + ) + genesis_denom = token_factory_genesis_pb.GenesisDenom( + denom="factory/inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0/ninja", + authority_metadata=authority_metadata, + name="Dog Wif Nunchucks", + symbol="NINJA", + ) + state = token_factory_genesis_pb.GenesisState( + params=params, + factory_denoms=[genesis_denom], + ) + token_factory_query_servicer.tokenfactory_module_state_responses.append( + token_factory_query_pb.QueryModuleStateResponse(state=state) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcTokenFactoryApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._query_stub = token_factory_query_servicer + + state = await api.fetch_tokenfactory_module_state() + expected_state = { + "state": { + "params": { + "denomCreationFee": [ + {"denom": coin.denom, "amount": coin.amount}, + ], + }, + "factoryDenoms": [ + { + "denom": genesis_denom.denom, + "authorityMetadata": { + "admin": authority_metadata.admin, + }, + "name": genesis_denom.name, + "symbol": genesis_denom.symbol, + } + ], + } + } + + assert state == expected_state + + async def _dummy_metadata_provider(self): + return None From e733927023ce94023baa896054372f3c8e5dba72 Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 3 Jan 2024 16:50:41 -0300 Subject: [PATCH 3/7] (feat) Added logic in Composer to create the Tokenfactory related messages to admin tokens. Added new example scripts for all the messages. --- examples/chain_client/71_CreateDenom.py | 35 ++++++ examples/chain_client/72_MsgMint.py | 38 ++++++ examples/chain_client/73_MsgBurn.py | 38 ++++++ .../chain_client/74_MsgSetDenomMetadata.py | 53 ++++++++ examples/chain_client/75_MsgUpdateParams.py | 37 ++++++ examples/chain_client/76_MsgChangeAdmin.py | 37 ++++++ pyinjective/composer.py | 95 +++++++++++++- tests/test_composer.py | 116 ++++++++++++++++++ 8 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 examples/chain_client/71_CreateDenom.py create mode 100644 examples/chain_client/72_MsgMint.py create mode 100644 examples/chain_client/73_MsgBurn.py create mode 100644 examples/chain_client/74_MsgSetDenomMetadata.py create mode 100644 examples/chain_client/75_MsgUpdateParams.py create mode 100644 examples/chain_client/76_MsgChangeAdmin.py diff --git a/examples/chain_client/71_CreateDenom.py b/examples/chain_client/71_CreateDenom.py new file mode 100644 index 00000000..61e76dbf --- /dev/null +++ b/examples/chain_client/71_CreateDenom.py @@ -0,0 +1,35 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + message = composer.msg_create_denom( + sender=address.to_acc_bech32(), subdenom="inj_test", name="Injective Test Token", symbol="INJTEST" + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/72_MsgMint.py b/examples/chain_client/72_MsgMint.py new file mode 100644 index 00000000..e96bad93 --- /dev/null +++ b/examples/chain_client/72_MsgMint.py @@ -0,0 +1,38 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + amount = composer.Coin(amount=1_000_000_000, denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test") + + message = composer.msg_mint( + sender=address.to_acc_bech32(), + amount=amount, + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/73_MsgBurn.py b/examples/chain_client/73_MsgBurn.py new file mode 100644 index 00000000..cff5a1b4 --- /dev/null +++ b/examples/chain_client/73_MsgBurn.py @@ -0,0 +1,38 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + amount = composer.Coin(amount=100, denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test") + + message = composer.msg_burn( + sender=address.to_acc_bech32(), + amount=amount, + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/74_MsgSetDenomMetadata.py b/examples/chain_client/74_MsgSetDenomMetadata.py new file mode 100644 index 00000000..58fa30c0 --- /dev/null +++ b/examples/chain_client/74_MsgSetDenomMetadata.py @@ -0,0 +1,53 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_without_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + sender = address.to_acc_bech32() + description = "Injective Test Token" + subdenom = "inj_test" + denom = "factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test" + token_decimals = 6 + name = "Injective Test" + symbol = "INJTEST" + uri = "http://injective-test.com/icon.jpg" + uri_hash = "" + + message = composer.msg_set_denom_metadata( + sender=sender, + description=description, + denom=denom, + subdenom=subdenom, + token_decimals=token_decimals, + name=name, + symbol=symbol, + uri=uri, + uri_hash=uri_hash, + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/75_MsgUpdateParams.py b/examples/chain_client/75_MsgUpdateParams.py new file mode 100644 index 00000000..0d53231b --- /dev/null +++ b/examples/chain_client/75_MsgUpdateParams.py @@ -0,0 +1,37 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + message = composer.msg_update_params( + authority=address.to_acc_bech32(), + denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test", + amount=1000, + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/76_MsgChangeAdmin.py b/examples/chain_client/76_MsgChangeAdmin.py new file mode 100644 index 00000000..2abc6ec6 --- /dev/null +++ b/examples/chain_client/76_MsgChangeAdmin.py @@ -0,0 +1,37 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_without_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + message = composer.msg_change_admin( + sender=address.to_acc_bech32(), + denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test", + new_admin="inj1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqe2hm49", # This is the zero address to remove admin permissions + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/pyinjective/composer.py b/pyinjective/composer.py index 56491b50..e7d2deff 100644 --- a/pyinjective/composer.py +++ b/pyinjective/composer.py @@ -11,7 +11,7 @@ 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.bank.v1beta1 import bank_pb2 as bank_pb, 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 @@ -28,6 +28,10 @@ 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 from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_query +from pyinjective.proto.injective.tokenfactory.v1beta1 import ( + params_pb2 as token_factory_params_pb, + tx_pb2 as token_factory_tx_pb, +) REQUEST_TO_RESPONSE_TYPE_MAP = { "MsgCreateSpotLimitOrder": injective_exchange_tx_pb.MsgCreateSpotLimitOrderResponse, @@ -957,6 +961,95 @@ def MsgInstantiateContract( # The coins in the list must be sorted in alphabetical order by denoms. ) + def msg_create_denom( + self, + sender: str, + subdenom: str, + name: str, + symbol: str, + ) -> token_factory_tx_pb.MsgCreateDenom: + return token_factory_tx_pb.MsgCreateDenom( + sender=sender, + subdenom=subdenom, + name=name, + symbol=symbol, + ) + + def msg_mint( + self, + sender: str, + amount: cosmos_dot_base_dot_v1beta1_dot_coin__pb2.Coin, + ) -> token_factory_tx_pb.MsgMint: + return token_factory_tx_pb.MsgMint(sender=sender, amount=amount) + + def msg_burn( + self, + sender: str, + amount: cosmos_dot_base_dot_v1beta1_dot_coin__pb2.Coin, + ) -> token_factory_tx_pb.MsgBurn: + return token_factory_tx_pb.MsgBurn(sender=sender, amount=amount) + + def msg_set_denom_metadata( + self, + sender: str, + description: str, + denom: str, + subdenom: str, + token_decimals: int, + name: str, + symbol: str, + uri: str, + uri_hash: str, + ) -> token_factory_tx_pb.MsgSetDenomMetadata: + micro_denom_unit = bank_pb.DenomUnit( + denom=denom, + exponent=0, + aliases=[f"micro{subdenom}"], + ) + denom_unit = bank_pb.DenomUnit( + denom=subdenom, + exponent=token_decimals, + aliases=[subdenom], + ) + metadata = bank_pb.Metadata( + description=description, + denom_units=[micro_denom_unit, denom_unit], + base=denom, + display=subdenom, + name=name, + symbol=symbol, + uri=uri, + uri_hash=uri_hash, + ) + return token_factory_tx_pb.MsgSetDenomMetadata(sender=sender, metadata=metadata) + + def msg_update_params( + self, + authority: str, + denom: str, + amount: int, + ) -> token_factory_tx_pb.MsgUpdateParams: + coin = self.Coin(amount=amount, denom=denom) + params = token_factory_params_pb.Params( + denom_creation_fee=[coin], + ) + return token_factory_tx_pb.MsgUpdateParams( + authority=authority, + params=params, + ) + + def msg_change_admin( + self, + sender: str, + denom: str, + new_admin: str, + ) -> token_factory_tx_pb.MsgChangeAdmin: + return token_factory_tx_pb.MsgChangeAdmin( + sender=sender, + denom=denom, + new_admin=new_admin, + ) + def chain_stream_bank_balances_filter( self, accounts: Optional[List[str]] = None ) -> chain_stream_query.BankBalancesFilter: diff --git a/tests/test_composer.py b/tests/test_composer.py index f3a9bdc2..392d0e7a 100644 --- a/tests/test_composer.py +++ b/tests/test_composer.py @@ -260,3 +260,119 @@ def test_buy_binary_option_order_creation_without_fixed_denom( assert order.order_type == exchange_pb2.OrderType.BUY assert order.margin == str(int(expected_margin)) assert order.trigger_price == "0" + + def test_msg_create_denom(self, basic_composer: Composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + subdenom = "inj-test" + name = "Injective Test" + symbol = "INJTEST" + + message = basic_composer.msg_create_denom( + sender=sender, + subdenom=subdenom, + name=name, + symbol=symbol, + ) + + assert message.sender == sender + assert message.subdenom == subdenom + assert message.name == name + assert message.symbol == symbol + + def test_msg_mint(self, basic_composer: Composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + amount = basic_composer.Coin( + amount=1_000_000, + denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test", + ) + + message = basic_composer.msg_mint( + sender=sender, + amount=amount, + ) + + assert message.sender == sender + assert message.amount == amount + + def test_msg_burn(self, basic_composer: Composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + amount = basic_composer.Coin( + amount=100, + denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test", + ) + + message = basic_composer.msg_burn( + sender=sender, + amount=amount, + ) + + assert message.sender == sender + assert message.amount == amount + + def test_msg_set_denom_metadata(self, basic_composer: Composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + description = "Injective Test Token" + subdenom = "inj_test" + denom = "factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test" + token_decimals = 6 + name = "Injective Test" + symbol = "INJTEST" + uri = "http://injective-test.com/icon.jpg" + uri_hash = "" + + message = basic_composer.msg_set_denom_metadata( + sender=sender, + description=description, + denom=denom, + subdenom=subdenom, + token_decimals=token_decimals, + name=name, + symbol=symbol, + uri=uri, + uri_hash=uri_hash, + ) + + assert message.sender == sender + assert message.metadata.description == description + assert message.metadata.denom_units[0].denom == denom + assert message.metadata.denom_units[0].exponent == 0 + assert message.metadata.denom_units[0].aliases == [f"micro{subdenom}"] + assert message.metadata.denom_units[1].denom == subdenom + assert message.metadata.denom_units[1].exponent == token_decimals + assert message.metadata.denom_units[1].aliases == [subdenom] + assert message.metadata.base == denom + assert message.metadata.display == subdenom + assert message.metadata.name == name + assert message.metadata.symbol == symbol + assert message.metadata.uri == uri + assert message.metadata.uri_hash == uri_hash + + def test_msg_update_params(self, basic_composer: Composer): + authority = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + amount = 1000 + denom = "factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test" + + message = basic_composer.msg_update_params( + authority=authority, + denom=denom, + amount=amount, + ) + + assert message.authority == authority + assert message.params.denom_creation_fee[0].amount == str(amount) + assert message.params.denom_creation_fee[0].denom == denom + + def test_msg_change_admin(self, basic_composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + denom = "factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test" + new_admin = "inj1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqe2hm49" + + message = basic_composer.msg_change_admin( + sender=sender, + denom=denom, + new_admin=new_admin, + ) + + assert message.sender == sender + assert message.denom == denom + assert message.new_admin == new_admin From 012ae22f1cac688682755f80fc82fa8fd7dc3e21 Mon Sep 17 00:00:00 2001 From: abel Date: Tue, 2 Jan 2024 15:35:21 -0300 Subject: [PATCH 4/7] (feat) Added support for missing bank module endpoints --- examples/chain_client/58_ContractInfo.py | 16 + examples/chain_client/59_ContractHistory.py | 20 + examples/chain_client/60_ContractsByCode.py | 20 + examples/chain_client/61_AllContractsState.py | 20 + examples/chain_client/62_RawContractState.py | 17 + .../chain_client/63_SmartContractState.py | 17 + examples/chain_client/64_SmartContractCode.py | 20 + .../chain_client/65_SmartContractCodes.py | 19 + .../66_SmartContractPinnedCodes.py | 19 + .../chain_client/67_ContractsByCreator.py | 20 + pyinjective/async_client.py | 76 +++ .../client/chain/grpc/chain_grpc_wasm_api.py | 144 +++++ .../grpc/configurable_wasm_query_servicer.py | 56 ++ .../chain/grpc/test_chain_grpc_wasm_api.py | 508 ++++++++++++++++++ 14 files changed, 972 insertions(+) create mode 100644 examples/chain_client/58_ContractInfo.py create mode 100644 examples/chain_client/59_ContractHistory.py create mode 100644 examples/chain_client/60_ContractsByCode.py create mode 100644 examples/chain_client/61_AllContractsState.py create mode 100644 examples/chain_client/62_RawContractState.py create mode 100644 examples/chain_client/63_SmartContractState.py create mode 100644 examples/chain_client/64_SmartContractCode.py create mode 100644 examples/chain_client/65_SmartContractCodes.py create mode 100644 examples/chain_client/66_SmartContractPinnedCodes.py create mode 100644 examples/chain_client/67_ContractsByCreator.py create mode 100644 pyinjective/client/chain/grpc/chain_grpc_wasm_api.py create mode 100644 tests/client/chain/grpc/configurable_wasm_query_servicer.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_wasm_api.py diff --git a/examples/chain_client/58_ContractInfo.py b/examples/chain_client/58_ContractInfo.py new file mode 100644 index 00000000..9cee904c --- /dev/null +++ b/examples/chain_client/58_ContractInfo.py @@ -0,0 +1,16 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + address = "inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7" + contract_info = await client.fetch_contract_info(address=address) + print(contract_info) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/59_ContractHistory.py b/examples/chain_client/59_ContractHistory.py new file mode 100644 index 00000000..460056a8 --- /dev/null +++ b/examples/chain_client/59_ContractHistory.py @@ -0,0 +1,20 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + address = "inj18pp4vjwucpgg4nw3rr4wh4zyjg9ct5t8v9wqgj" + limit = 2 + pagination = PaginationOption(limit=limit) + contract_history = await client.fetch_contract_history(address=address, pagination=pagination) + print(contract_history) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/60_ContractsByCode.py b/examples/chain_client/60_ContractsByCode.py new file mode 100644 index 00000000..1994ce87 --- /dev/null +++ b/examples/chain_client/60_ContractsByCode.py @@ -0,0 +1,20 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + code_id = 3770 + limit = 2 + pagination = PaginationOption(limit=limit) + contracts = await client.fetch_contracts_by_code(code_id=code_id, pagination=pagination) + print(contracts) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/61_AllContractsState.py b/examples/chain_client/61_AllContractsState.py new file mode 100644 index 00000000..a96bfe40 --- /dev/null +++ b/examples/chain_client/61_AllContractsState.py @@ -0,0 +1,20 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + address = "inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7" + limit = 2 + pagination = PaginationOption(limit=limit) + contract_history = await client.fetch_all_contracts_state(address=address, pagination=pagination) + print(contract_history) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/62_RawContractState.py b/examples/chain_client/62_RawContractState.py new file mode 100644 index 00000000..5c9bce71 --- /dev/null +++ b/examples/chain_client/62_RawContractState.py @@ -0,0 +1,17 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + address = "inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7" + query_data = '{"get_count": {}}' + contract_state = await client.fetch_raw_contract_state(address=address, query_data=query_data) + print(contract_state) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/63_SmartContractState.py b/examples/chain_client/63_SmartContractState.py new file mode 100644 index 00000000..b416d88c --- /dev/null +++ b/examples/chain_client/63_SmartContractState.py @@ -0,0 +1,17 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + address = "inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7" + query_data = '{"get_count": {}}' + contract_state = await client.fetch_smart_contract_state(address=address, query_data=query_data) + print(contract_state) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/64_SmartContractCode.py b/examples/chain_client/64_SmartContractCode.py new file mode 100644 index 00000000..d523605d --- /dev/null +++ b/examples/chain_client/64_SmartContractCode.py @@ -0,0 +1,20 @@ +import asyncio +import base64 + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + response = await client.fetch_code(code_id=290) + print(response) + + code = base64.b64decode(response["data"]).decode(encoding="utf-8", errors="replace") + + print(f"\n\n\n{code}") + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/65_SmartContractCodes.py b/examples/chain_client/65_SmartContractCodes.py new file mode 100644 index 00000000..96135ba3 --- /dev/null +++ b/examples/chain_client/65_SmartContractCodes.py @@ -0,0 +1,19 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + limit = 2 + pagination = PaginationOption(limit=limit) + response = await client.fetch_codes(pagination=pagination) + print(response) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/66_SmartContractPinnedCodes.py b/examples/chain_client/66_SmartContractPinnedCodes.py new file mode 100644 index 00000000..9d435314 --- /dev/null +++ b/examples/chain_client/66_SmartContractPinnedCodes.py @@ -0,0 +1,19 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + limit = 2 + pagination = PaginationOption(limit=limit) + response = await client.fetch_pinned_codes(pagination=pagination) + print(response) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/67_ContractsByCreator.py b/examples/chain_client/67_ContractsByCreator.py new file mode 100644 index 00000000..2014eee1 --- /dev/null +++ b/examples/chain_client/67_ContractsByCreator.py @@ -0,0 +1,20 @@ +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: + # select network: local, testnet, mainnet + network = Network.testnet() + client = AsyncClient(network) + creator = "inj1h3gepa4tszh66ee67he53jzmprsqc2l9npq3ty" + limit = 2 + pagination = PaginationOption(limit=limit) + response = await client.fetch_contracts_by_creator(creator_address=creator, pagination=pagination) + print(response) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 50eddecf..66892d89 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -13,6 +13,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.chain_grpc_wasm_api import ChainGrpcWasmApi 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 @@ -183,6 +184,12 @@ def __init__( metadata_query_provider=self._chain_cookie_metadata_requestor ), ) + self.wasm_api = ChainGrpcWasmApi( + channel=self.chain_channel, + metadata_provider=lambda: self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) self.chain_stream_api = ChainGrpcChainStream( channel=self.chain_stream_channel, @@ -662,6 +669,75 @@ async def listen_keepalive( on_status_callback=on_status_callback, ) + # Wasm module + async def fetch_contract_info(self, address: str) -> Dict[str, Any]: + return await self.wasm_api.fetch_contract_info(address=address) + + async def fetch_contract_history( + self, + address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_contract_history( + address=address, + pagination=pagination, + ) + + async def fetch_contracts_by_code( + self, + code_id: int, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_contracts_by_code( + code_id=code_id, + pagination=pagination, + ) + + async def fetch_all_contracts_state( + self, + address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_all_contracts_state( + address=address, + pagination=pagination, + ) + + async def fetch_raw_contract_state(self, address: str, query_data: str) -> Dict[str, Any]: + return await self.wasm_api.fetch_raw_contract_state(address=address, query_data=query_data) + + async def fetch_smart_contract_state(self, address: str, query_data: str) -> Dict[str, Any]: + return await self.wasm_api.fetch_smart_contract_state(address=address, query_data=query_data) + + async def fetch_code(self, code_id: int) -> Dict[str, Any]: + return await self.wasm_api.fetch_code(code_id=code_id) + + async def fetch_codes( + self, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_codes( + pagination=pagination, + ) + + async def fetch_pinned_codes( + self, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_pinned_codes( + pagination=pagination, + ) + + async def fetch_contracts_by_creator( + self, + creator_address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.wasm_api.fetch_contracts_by_creator( + creator_address=creator_address, + pagination=pagination, + ) + # Explorer RPC async def get_tx_by_hash(self, tx_hash: str): diff --git a/pyinjective/client/chain/grpc/chain_grpc_wasm_api.py b/pyinjective/client/chain/grpc/chain_grpc_wasm_api.py new file mode 100644 index 00000000..1d08e1c3 --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_wasm_api.py @@ -0,0 +1,144 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.cosmwasm.wasm.v1 import query_pb2 as wasm_query_pb, query_pb2_grpc as wasm_query_grpc +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class ChainGrpcWasmApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = wasm_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = wasm_query_pb.QueryParamsRequest() + response = await self._execute_call(call=self._stub.Params, request=request) + + return response + + async def fetch_contract_info(self, address: str) -> Dict[str, Any]: + request = wasm_query_pb.QueryContractInfoRequest(address=address) + response = await self._execute_call(call=self._stub.ContractInfo, request=request) + + return response + + async def fetch_contract_history( + self, + address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryContractHistoryRequest( + address=address, + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.ContractHistory, request=request) + + return response + + async def fetch_contracts_by_code( + self, + code_id: int, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryContractsByCodeRequest( + code_id=code_id, + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.ContractsByCode, request=request) + + return response + + async def fetch_all_contracts_state( + self, + address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryAllContractStateRequest( + address=address, + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.AllContractState, request=request) + + return response + + async def fetch_raw_contract_state(self, address: str, query_data: str) -> Dict[str, Any]: + request = wasm_query_pb.QueryRawContractStateRequest( + address=address, + query_data=query_data.encode(), + ) + response = await self._execute_call(call=self._stub.RawContractState, request=request) + + return response + + async def fetch_smart_contract_state(self, address: str, query_data: str) -> Dict[str, Any]: + request = wasm_query_pb.QuerySmartContractStateRequest( + address=address, + query_data=query_data.encode(), + ) + response = await self._execute_call(call=self._stub.SmartContractState, request=request) + + return response + + async def fetch_code(self, code_id: int) -> Dict[str, Any]: + request = wasm_query_pb.QueryCodeRequest(code_id=code_id) + response = await self._execute_call(call=self._stub.Code, request=request) + + return response + + async def fetch_codes( + self, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryCodesRequest( + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.Codes, request=request) + + return response + + async def fetch_pinned_codes( + self, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryPinnedCodesRequest( + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.PinnedCodes, request=request) + + return response + + async def fetch_contracts_by_creator( + self, + creator_address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = wasm_query_pb.QueryContractsByCreatorRequest( + creator_address=creator_address, + pagination=pagination_request, + ) + response = await self._execute_call(call=self._stub.ContractsByCreator, 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/configurable_wasm_query_servicer.py b/tests/client/chain/grpc/configurable_wasm_query_servicer.py new file mode 100644 index 00000000..8b087e4c --- /dev/null +++ b/tests/client/chain/grpc/configurable_wasm_query_servicer.py @@ -0,0 +1,56 @@ +from collections import deque + +from pyinjective.proto.cosmwasm.wasm.v1 import query_pb2 as wasm_query_pb, query_pb2_grpc as wasm_query_grpc + + +class ConfigurableWasmQueryServicer(wasm_query_grpc.QueryServicer): + def __init__(self): + super().__init__() + self.params_responses = deque() + self.contract_info_responses = deque() + self.contract_history_responses = deque() + self.contracts_by_code_responses = deque() + self.all_contracts_state_responses = deque() + self.raw_contract_state_responses = deque() + self.smart_contract_state_responses = deque() + self.code_responses = deque() + self.codes_responses = deque() + self.pinned_codes_responses = deque() + self.contracts_by_creator_responses = deque() + + async def Params(self, request: wasm_query_pb.QueryParamsRequest, context=None, metadata=None): + return self.params_responses.pop() + + async def ContractInfo(self, request: wasm_query_pb.QueryContractInfoRequest, context=None, metadata=None): + return self.contract_info_responses.pop() + + async def ContractHistory(self, request: wasm_query_pb.QueryContractHistoryRequest, context=None, metadata=None): + return self.contract_history_responses.pop() + + async def ContractsByCode(self, request: wasm_query_pb.QueryContractsByCodeRequest, context=None, metadata=None): + return self.contracts_by_code_responses.pop() + + async def AllContractState(self, request: wasm_query_pb.QueryAllContractStateRequest, context=None, metadata=None): + return self.all_contracts_state_responses.pop() + + async def RawContractState(self, request: wasm_query_pb.QueryRawContractStateRequest, context=None, metadata=None): + return self.raw_contract_state_responses.pop() + + async def SmartContractState( + self, request: wasm_query_pb.QuerySmartContractStateRequest, context=None, metadata=None + ): + return self.smart_contract_state_responses.pop() + + async def Code(self, request: wasm_query_pb.QueryCodeRequest, context=None, metadata=None): + return self.code_responses.pop() + + async def Codes(self, request: wasm_query_pb.QueryCodesRequest, context=None, metadata=None): + return self.codes_responses.pop() + + async def PinnedCodes(self, request: wasm_query_pb.QueryPinnedCodesRequest, context=None, metadata=None): + return self.pinned_codes_responses.pop() + + async def ContractsByCreator( + self, request: wasm_query_pb.QueryContractsByCreatorRequest, context=None, metadata=None + ): + return self.contracts_by_creator_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_wasm_api.py b/tests/client/chain/grpc/test_chain_grpc_wasm_api.py new file mode 100644 index 00000000..9822d1ee --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_wasm_api.py @@ -0,0 +1,508 @@ +import base64 +import json + +import grpc +import pytest + +from pyinjective.client.chain.grpc.chain_grpc_wasm_api import ChainGrpcWasmApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from pyinjective.proto.cosmwasm.wasm.v1 import query_pb2 as wasm_query_pb, types_pb2 as wasm_types_pb +from tests.client.chain.grpc.configurable_wasm_query_servicer import ConfigurableWasmQueryServicer + + +@pytest.fixture +def wasm_servicer(): + return ConfigurableWasmQueryServicer() + + +class TestChainGrpcBankApi: + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + wasm_servicer, + ): + access_config = wasm_types_pb.AccessConfig( + permission=0, + addresses=["inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7"], + ) + params = wasm_types_pb.Params( + code_upload_access=access_config, + instantiate_default_permission=0, + ) + wasm_servicer.params_responses.append(wasm_query_pb.QueryParamsResponse(params=params)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + module_params = await api.fetch_module_params() + expected_params = { + "params": { + "codeUploadAccess": { + "permission": wasm_types_pb.AccessType.Name(access_config.permission), + "address": "", + "addresses": access_config.addresses, + }, + "instantiateDefaultPermission": wasm_types_pb.AccessType.Name(params.instantiate_default_permission), + } + } + + assert expected_params == module_params + + @pytest.mark.asyncio + async def test_fetch_contract_info( + self, + wasm_servicer, + ): + address = "inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r" + tx_position = wasm_types_pb.AbsoluteTxPosition( + block_height=1234, + tx_index=9999, + ) + contract_info = wasm_types_pb.ContractInfo( + code_id=10, + creator="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + admin="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + label="test label", + created=tx_position, + ibc_port_id="ibc port id", + ) + wasm_servicer.contract_info_responses.append( + wasm_query_pb.QueryContractInfoResponse( + address=address, + contract_info=contract_info, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_contract_info(address=address) + expected_contract_info = { + "address": address, + "contractInfo": { + "codeId": str(contract_info.code_id), + "creator": contract_info.creator, + "admin": contract_info.admin, + "label": contract_info.label, + "created": { + "blockHeight": str(tx_position.block_height), + "txIndex": str(tx_position.tx_index), + }, + "ibcPortId": contract_info.ibc_port_id, + }, + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_contract_history( + self, + wasm_servicer, + ): + tx_position = wasm_types_pb.AbsoluteTxPosition( + block_height=1234, + tx_index=9999, + ) + history_entry = wasm_types_pb.ContractCodeHistoryEntry( + operation=0, + code_id=3770, + updated=tx_position, + msg="raw message test".encode(), + ) + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.contract_history_responses.append( + wasm_query_pb.QueryContractHistoryResponse( + entries=[history_entry], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_contract_history( + address="inj18pp4vjwucpgg4nw3rr4wh4zyjg9ct5t8v9wqgj", + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_contract_info = { + "entries": [ + { + "operation": wasm_types_pb.ContractCodeHistoryOperationType.Name(history_entry.operation), + "codeId": str(history_entry.code_id), + "updated": { + "blockHeight": str(tx_position.block_height), + "txIndex": str(tx_position.tx_index), + }, + "msg": base64.b64encode(history_entry.msg).decode(), + } + ], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_contracts_by_code( + self, + wasm_servicer, + ): + contract = "inj1z4l7jc8dj3y9484aqcrmf6y8mcctvkmm9zkf7n" + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.contracts_by_code_responses.append( + wasm_query_pb.QueryContractsByCodeResponse( + contracts=[contract], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_contracts_by_code( + code_id=3770, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_contract_info = { + "contracts": [contract], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_all_contracts_state( + self, + wasm_servicer, + ): + model = wasm_types_pb.Model( + key=( + "\x00\x08redeemed\x00*inj13t085sclq8fxy8d3gcjt3jap45j4fwlc79lykw" "\x00\x00\x00\x00\x00\x00\x00\x00" + ).encode(), + value='{"phase_id":0,"redeemed":1,"mint_limit":1}'.encode(), + ) + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.all_contracts_state_responses.append( + wasm_query_pb.QueryAllContractStateResponse( + models=[model], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_all_contracts_state( + address="inj1z4l7jc8dj3y9484aqcrmf6y8mcctvkmm9zkf7n", + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_contract_info = { + "models": [{"key": base64.b64encode(model.key).decode(), "value": base64.b64encode(model.value).decode()}], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_raw_contract_state( + self, + wasm_servicer, + ): + data = "test data".encode() + wasm_servicer.raw_contract_state_responses.append( + wasm_query_pb.QueryRawContractStateResponse( + data=data, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_raw_contract_state( + address="inj1z4l7jc8dj3y9484aqcrmf6y8mcctvkmm9zkf7n", + query_data="query data", + ) + expected_contract_info = { + "data": base64.b64encode(data).decode(), + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_smart_contract_state( + self, + wasm_servicer, + ): + data = json.dumps({"count": 1037}).encode() + wasm_servicer.smart_contract_state_responses.append( + wasm_query_pb.QuerySmartContractStateResponse( + data=data, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_smart_contract_state( + address="inj1z4l7jc8dj3y9484aqcrmf6y8mcctvkmm9zkf7n", + query_data="query data", + ) + expected_contract_info = { + "data": base64.b64encode(data).decode(), + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_code( + self, + wasm_servicer, + ): + access_config = wasm_types_pb.AccessConfig( + permission=0, + addresses=["inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7"], + ) + code_info_response = wasm_query_pb.CodeInfoResponse( + code_id=290, + creator="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + data_hash="0xf8e7689e23ac0c9d53f44a8fd98c686c20b0140a8d76d600e2c546bfbba7758d".encode(), + instantiate_permission=access_config, + ) + data = "code".encode() + wasm_servicer.code_responses.append( + wasm_query_pb.QueryCodeResponse( + code_info=code_info_response, + data=data, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + info = await api.fetch_code(code_id=code_info_response.code_id) + expected_contract_info = { + "codeInfo": { + "codeId": str(code_info_response.code_id), + "creator": code_info_response.creator, + "dataHash": base64.b64encode(code_info_response.data_hash).decode(), + "instantiatePermission": { + "permission": wasm_types_pb.AccessType.Name(access_config.permission), + "address": "", + "addresses": access_config.addresses, + }, + }, + "data": base64.b64encode(data).decode(), + } + + assert info == expected_contract_info + + @pytest.mark.asyncio + async def test_fetch_codes( + self, + wasm_servicer, + ): + access_config = wasm_types_pb.AccessConfig( + permission=0, + addresses=["inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7"], + ) + code_info_response = wasm_query_pb.CodeInfoResponse( + code_id=290, + creator="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + data_hash="0xf8e7689e23ac0c9d53f44a8fd98c686c20b0140a8d76d600e2c546bfbba7758d".encode(), + instantiate_permission=access_config, + ) + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.codes_responses.append( + wasm_query_pb.QueryCodesResponse( + code_infos=[code_info_response], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + codes = await api.fetch_codes( + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_codes = { + "codeInfos": [ + { + "codeId": str(code_info_response.code_id), + "creator": code_info_response.creator, + "dataHash": base64.b64encode(code_info_response.data_hash).decode(), + "instantiatePermission": { + "permission": wasm_types_pb.AccessType.Name(access_config.permission), + "address": "", + "addresses": access_config.addresses, + }, + }, + ], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert codes == expected_codes + + @pytest.mark.asyncio + async def test_fetch_pinned_codes( + self, + wasm_servicer, + ): + code_id = 290 + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.pinned_codes_responses.append( + wasm_query_pb.QueryPinnedCodesResponse( + code_ids=[code_id], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + codes = await api.fetch_pinned_codes( + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_codes = { + "codeIds": [str(code_id)], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert codes == expected_codes + + @pytest.mark.asyncio + async def test_contracts_by_creator( + self, + wasm_servicer, + ): + contract_address = "inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7" + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + wasm_servicer.contracts_by_creator_responses.append( + wasm_query_pb.QueryContractsByCreatorResponse( + contract_addresses=[contract_address], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcWasmApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = wasm_servicer + + codes = await api.fetch_contracts_by_creator( + creator_address="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_codes = { + "contractAddresses": [contract_address], + "pagination": { + "nextKey": base64.b64encode(pagination.next_key).decode(), + "total": "179", + }, + } + + assert codes == expected_codes + + async def _dummy_metadata_provider(self): + return None From a9e14d8728196a4597ccbd3090ecbce590c244c3 Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 3 Jan 2024 12:36:32 -0300 Subject: [PATCH 5/7] (feat) Implemented all TokenFactory query module endpoints --- .../chain_client/68_AuthorityDenomMetadata.py | 15 ++ examples/chain_client/69_DenomsFromCreator.py | 15 ++ .../70_TokenfactoryModuleState.py | 15 ++ pyinjective/async_client.py | 25 +++ .../grpc/chain_grpc_token_factory_api.py | 51 ++++++ ...nfigurable_token_factory_query_servicer.py | 33 ++++ .../grpc/test_chain_grpc_token_factory_api.py | 159 ++++++++++++++++++ 7 files changed, 313 insertions(+) create mode 100644 examples/chain_client/68_AuthorityDenomMetadata.py create mode 100644 examples/chain_client/69_DenomsFromCreator.py create mode 100644 examples/chain_client/70_TokenfactoryModuleState.py create mode 100644 pyinjective/client/chain/grpc/chain_grpc_token_factory_api.py create mode 100644 tests/client/chain/grpc/configurable_token_factory_query_servicer.py create mode 100644 tests/client/chain/grpc/test_chain_grpc_token_factory_api.py diff --git a/examples/chain_client/68_AuthorityDenomMetadata.py b/examples/chain_client/68_AuthorityDenomMetadata.py new file mode 100644 index 00000000..f5ebaab5 --- /dev/null +++ b/examples/chain_client/68_AuthorityDenomMetadata.py @@ -0,0 +1,15 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + metadata = await client.fetch_denom_authority_metadata(creator="inj1zvy8xrlhe7ex9scer868clfstdv7j6vz790kwa") + print(metadata) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/69_DenomsFromCreator.py b/examples/chain_client/69_DenomsFromCreator.py new file mode 100644 index 00000000..17a486d1 --- /dev/null +++ b/examples/chain_client/69_DenomsFromCreator.py @@ -0,0 +1,15 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + denoms = await client.fetch_denoms_from_creator(creator="inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3") + print(denoms) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/70_TokenfactoryModuleState.py b/examples/chain_client/70_TokenfactoryModuleState.py new file mode 100644 index 00000000..8f9b08f3 --- /dev/null +++ b/examples/chain_client/70_TokenfactoryModuleState.py @@ -0,0 +1,15 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + state = await client.fetch_tokenfactory_module_state() + print(state) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 66892d89..ec42c2ce 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -13,6 +13,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.chain_grpc_token_factory_api import ChainGrpcTokenFactoryApi from pyinjective.client.chain.grpc.chain_grpc_wasm_api import ChainGrpcWasmApi from pyinjective.client.chain.grpc_stream.chain_grpc_chain_stream import ChainGrpcChainStream from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi @@ -178,6 +179,12 @@ def __init__( metadata_query_provider=self._chain_cookie_metadata_requestor ), ) + self.token_factory_api = ChainGrpcTokenFactoryApi( + channel=self.chain_channel, + 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=lambda: self.network.chain_metadata( @@ -738,6 +745,24 @@ async def fetch_contracts_by_creator( pagination=pagination, ) + # Token Factory module + + async def fetch_denom_authority_metadata( + self, + creator: str, + sub_denom: Optional[str] = None, + ) -> Dict[str, Any]: + return await self.token_factory_api.fetch_denom_authority_metadata(creator=creator, sub_denom=sub_denom) + + async def fetch_denoms_from_creator( + self, + creator: str, + ) -> Dict[str, Any]: + return await self.token_factory_api.fetch_denoms_from_creator(creator=creator) + + async def fetch_tokenfactory_module_state(self) -> Dict[str, Any]: + return await self.token_factory_api.fetch_tokenfactory_module_state() + # Explorer RPC async def get_tx_by_hash(self, tx_hash: str): diff --git a/pyinjective/client/chain/grpc/chain_grpc_token_factory_api.py b/pyinjective/client/chain/grpc/chain_grpc_token_factory_api.py new file mode 100644 index 00000000..48e1df1c --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_token_factory_api.py @@ -0,0 +1,51 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.proto.injective.tokenfactory.v1beta1 import ( + query_pb2 as token_factory_query_pb, + query_pb2_grpc as token_factory_query_grpc, + tx_pb2_grpc as token_factory_tx_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class ChainGrpcTokenFactoryApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._query_stub = token_factory_query_grpc.QueryStub(channel) + self._tx_stub = token_factory_tx_grpc.MsgStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = token_factory_query_pb.QueryParamsRequest() + response = await self._execute_call(call=self._query_stub.Params, request=request) + + return response + + async def fetch_denom_authority_metadata( + self, + creator: str, + sub_denom: Optional[str] = None, + ) -> Dict[str, Any]: + request = token_factory_query_pb.QueryDenomAuthorityMetadataRequest( + creator=creator, + sub_denom=sub_denom, + ) + response = await self._execute_call(call=self._query_stub.DenomAuthorityMetadata, request=request) + + return response + + async def fetch_denoms_from_creator(self, creator: str) -> Dict[str, Any]: + request = token_factory_query_pb.QueryDenomsFromCreatorRequest(creator=creator) + response = await self._execute_call(call=self._query_stub.DenomsFromCreator, request=request) + + return response + + async def fetch_tokenfactory_module_state(self) -> Dict[str, Any]: + request = token_factory_query_pb.QueryModuleStateRequest() + response = await self._execute_call(call=self._query_stub.TokenfactoryModuleState, 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/configurable_token_factory_query_servicer.py b/tests/client/chain/grpc/configurable_token_factory_query_servicer.py new file mode 100644 index 00000000..56fb8d58 --- /dev/null +++ b/tests/client/chain/grpc/configurable_token_factory_query_servicer.py @@ -0,0 +1,33 @@ +from collections import deque + +from pyinjective.proto.injective.tokenfactory.v1beta1 import ( + query_pb2 as token_factory_query_pb, + query_pb2_grpc as token_factory_query_grpc, +) + + +class ConfigurableTokenFactoryQueryServicer(token_factory_query_grpc.QueryServicer): + def __init__(self): + super().__init__() + self.params_responses = deque() + self.denom_authority_metadata_responses = deque() + self.denoms_from_creator_responses = deque() + self.tokenfactory_module_state_responses = deque() + + async def Params(self, request: token_factory_query_pb.QueryParamsRequest, context=None, metadata=None): + return self.params_responses.pop() + + async def DenomAuthorityMetadata( + self, request: token_factory_query_pb.QueryDenomAuthorityMetadataRequest, context=None, metadata=None + ): + return self.denom_authority_metadata_responses.pop() + + async def DenomsFromCreator( + self, request: token_factory_query_pb.QueryDenomsFromCreatorRequest, context=None, metadata=None + ): + return self.denoms_from_creator_responses.pop() + + async def TokenfactoryModuleState( + self, request: token_factory_query_pb.QueryModuleStateRequest, context=None, metadata=None + ): + return self.tokenfactory_module_state_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_token_factory_api.py b/tests/client/chain/grpc/test_chain_grpc_token_factory_api.py new file mode 100644 index 00000000..418dfb35 --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_token_factory_api.py @@ -0,0 +1,159 @@ +import grpc +import pytest + +from pyinjective.client.chain.grpc.chain_grpc_token_factory_api import ChainGrpcTokenFactoryApi +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.injective.tokenfactory.v1beta1 import ( + authorityMetadata_pb2 as token_factory_authority_metadata_pb, + genesis_pb2 as token_factory_genesis_pb, + params_pb2 as token_factory_params_pb, + query_pb2 as token_factory_query_pb, +) +from tests.client.chain.grpc.configurable_token_factory_query_servicer import ConfigurableTokenFactoryQueryServicer + + +@pytest.fixture +def token_factory_query_servicer(): + return ConfigurableTokenFactoryQueryServicer() + + +class TestChainGrpcTokenFactoryApi: + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + token_factory_query_servicer, + ): + coin = coin_pb.Coin(denom="inj", amount="988987297011197594664") + params = token_factory_params_pb.Params( + denom_creation_fee=[coin], + ) + token_factory_query_servicer.params_responses.append(token_factory_query_pb.QueryParamsResponse(params=params)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcTokenFactoryApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._query_stub = token_factory_query_servicer + + module_params = await api.fetch_module_params() + expected_params = { + "params": { + "denomCreationFee": [ + {"denom": coin.denom, "amount": coin.amount}, + ], + } + } + + assert module_params == expected_params + + @pytest.mark.asyncio + async def test_fetch_denom_authority_metadata( + self, + token_factory_query_servicer, + ): + authority_metadata = token_factory_authority_metadata_pb.DenomAuthorityMetadata( + admin="inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7", + ) + token_factory_query_servicer.denom_authority_metadata_responses.append( + token_factory_query_pb.QueryDenomAuthorityMetadataResponse( + authority_metadata=authority_metadata, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcTokenFactoryApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._query_stub = token_factory_query_servicer + + metadata = await api.fetch_denom_authority_metadata( + creator=authority_metadata.admin, + sub_denom="inj", + ) + expected_metadata = { + "authorityMetadata": { + "admin": authority_metadata.admin, + }, + } + + assert metadata == expected_metadata + + @pytest.mark.asyncio + async def test_fetch_denoms_from_creator( + self, + token_factory_query_servicer, + ): + denom = "factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth" + token_factory_query_servicer.denoms_from_creator_responses.append( + token_factory_query_pb.QueryDenomsFromCreatorResponse(denoms=[denom]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcTokenFactoryApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._query_stub = token_factory_query_servicer + + denoms = await api.fetch_denoms_from_creator(creator="inj1ady3s7whq30l4fx8sj3x6muv5mx4dfdlcpv8n7") + expected_denoms = {"denoms": [denom]} + + assert denoms == expected_denoms + + @pytest.mark.asyncio + async def test_fetch_tokenfactory_module_state( + self, + token_factory_query_servicer, + ): + coin = coin_pb.Coin(denom="inj", amount="988987297011197594664") + params = token_factory_params_pb.Params( + denom_creation_fee=[coin], + ) + authority_metadata = token_factory_authority_metadata_pb.DenomAuthorityMetadata( + admin="inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0", + ) + genesis_denom = token_factory_genesis_pb.GenesisDenom( + denom="factory/inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0/ninja", + authority_metadata=authority_metadata, + name="Dog Wif Nunchucks", + symbol="NINJA", + ) + state = token_factory_genesis_pb.GenesisState( + params=params, + factory_denoms=[genesis_denom], + ) + token_factory_query_servicer.tokenfactory_module_state_responses.append( + token_factory_query_pb.QueryModuleStateResponse(state=state) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcTokenFactoryApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._query_stub = token_factory_query_servicer + + state = await api.fetch_tokenfactory_module_state() + expected_state = { + "state": { + "params": { + "denomCreationFee": [ + {"denom": coin.denom, "amount": coin.amount}, + ], + }, + "factoryDenoms": [ + { + "denom": genesis_denom.denom, + "authorityMetadata": { + "admin": authority_metadata.admin, + }, + "name": genesis_denom.name, + "symbol": genesis_denom.symbol, + } + ], + } + } + + assert state == expected_state + + async def _dummy_metadata_provider(self): + return None From e94940d8dcd9cd2a41c7c1c13cf82947f51c526e Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 3 Jan 2024 16:50:41 -0300 Subject: [PATCH 6/7] (feat) Added logic in Composer to create the Tokenfactory related messages to admin tokens. Added new example scripts for all the messages. --- examples/chain_client/71_CreateDenom.py | 35 ++++++ examples/chain_client/72_MsgMint.py | 38 ++++++ examples/chain_client/73_MsgBurn.py | 38 ++++++ .../chain_client/74_MsgSetDenomMetadata.py | 53 ++++++++ examples/chain_client/75_MsgUpdateParams.py | 37 ++++++ examples/chain_client/76_MsgChangeAdmin.py | 37 ++++++ pyinjective/composer.py | 95 +++++++++++++- tests/test_composer.py | 116 ++++++++++++++++++ 8 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 examples/chain_client/71_CreateDenom.py create mode 100644 examples/chain_client/72_MsgMint.py create mode 100644 examples/chain_client/73_MsgBurn.py create mode 100644 examples/chain_client/74_MsgSetDenomMetadata.py create mode 100644 examples/chain_client/75_MsgUpdateParams.py create mode 100644 examples/chain_client/76_MsgChangeAdmin.py diff --git a/examples/chain_client/71_CreateDenom.py b/examples/chain_client/71_CreateDenom.py new file mode 100644 index 00000000..61e76dbf --- /dev/null +++ b/examples/chain_client/71_CreateDenom.py @@ -0,0 +1,35 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + message = composer.msg_create_denom( + sender=address.to_acc_bech32(), subdenom="inj_test", name="Injective Test Token", symbol="INJTEST" + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/72_MsgMint.py b/examples/chain_client/72_MsgMint.py new file mode 100644 index 00000000..e96bad93 --- /dev/null +++ b/examples/chain_client/72_MsgMint.py @@ -0,0 +1,38 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + amount = composer.Coin(amount=1_000_000_000, denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test") + + message = composer.msg_mint( + sender=address.to_acc_bech32(), + amount=amount, + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/73_MsgBurn.py b/examples/chain_client/73_MsgBurn.py new file mode 100644 index 00000000..cff5a1b4 --- /dev/null +++ b/examples/chain_client/73_MsgBurn.py @@ -0,0 +1,38 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + amount = composer.Coin(amount=100, denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test") + + message = composer.msg_burn( + sender=address.to_acc_bech32(), + amount=amount, + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/74_MsgSetDenomMetadata.py b/examples/chain_client/74_MsgSetDenomMetadata.py new file mode 100644 index 00000000..58fa30c0 --- /dev/null +++ b/examples/chain_client/74_MsgSetDenomMetadata.py @@ -0,0 +1,53 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_without_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + sender = address.to_acc_bech32() + description = "Injective Test Token" + subdenom = "inj_test" + denom = "factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test" + token_decimals = 6 + name = "Injective Test" + symbol = "INJTEST" + uri = "http://injective-test.com/icon.jpg" + uri_hash = "" + + message = composer.msg_set_denom_metadata( + sender=sender, + description=description, + denom=denom, + subdenom=subdenom, + token_decimals=token_decimals, + name=name, + symbol=symbol, + uri=uri, + uri_hash=uri_hash, + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/75_MsgUpdateParams.py b/examples/chain_client/75_MsgUpdateParams.py new file mode 100644 index 00000000..0d53231b --- /dev/null +++ b/examples/chain_client/75_MsgUpdateParams.py @@ -0,0 +1,37 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + message = composer.msg_update_params( + authority=address.to_acc_bech32(), + denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test", + amount=1000, + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/76_MsgChangeAdmin.py b/examples/chain_client/76_MsgChangeAdmin.py new file mode 100644 index 00000000..2abc6ec6 --- /dev/null +++ b/examples/chain_client/76_MsgChangeAdmin.py @@ -0,0 +1,37 @@ +import asyncio + +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + # select network: local, testnet, mainnet + network = Network.testnet() + composer = ProtoMsgComposer(network=network.string()) + private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" + + message_broadcaster = MsgBroadcasterWithPk.new_without_simulation( + network=network, + private_key=private_key_in_hexa, + ) + + priv_key = PrivateKey.from_hex(private_key_in_hexa) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + + message = composer.msg_change_admin( + sender=address.to_acc_bech32(), + denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test", + new_admin="inj1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqe2hm49", # This is the zero address to remove admin permissions + ) + + # broadcast the transaction + result = await message_broadcaster.broadcast([message]) + print("---Transaction Response---") + print(result) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/pyinjective/composer.py b/pyinjective/composer.py index 56491b50..e7d2deff 100644 --- a/pyinjective/composer.py +++ b/pyinjective/composer.py @@ -11,7 +11,7 @@ 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.bank.v1beta1 import bank_pb2 as bank_pb, 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 @@ -28,6 +28,10 @@ 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 from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_query +from pyinjective.proto.injective.tokenfactory.v1beta1 import ( + params_pb2 as token_factory_params_pb, + tx_pb2 as token_factory_tx_pb, +) REQUEST_TO_RESPONSE_TYPE_MAP = { "MsgCreateSpotLimitOrder": injective_exchange_tx_pb.MsgCreateSpotLimitOrderResponse, @@ -957,6 +961,95 @@ def MsgInstantiateContract( # The coins in the list must be sorted in alphabetical order by denoms. ) + def msg_create_denom( + self, + sender: str, + subdenom: str, + name: str, + symbol: str, + ) -> token_factory_tx_pb.MsgCreateDenom: + return token_factory_tx_pb.MsgCreateDenom( + sender=sender, + subdenom=subdenom, + name=name, + symbol=symbol, + ) + + def msg_mint( + self, + sender: str, + amount: cosmos_dot_base_dot_v1beta1_dot_coin__pb2.Coin, + ) -> token_factory_tx_pb.MsgMint: + return token_factory_tx_pb.MsgMint(sender=sender, amount=amount) + + def msg_burn( + self, + sender: str, + amount: cosmos_dot_base_dot_v1beta1_dot_coin__pb2.Coin, + ) -> token_factory_tx_pb.MsgBurn: + return token_factory_tx_pb.MsgBurn(sender=sender, amount=amount) + + def msg_set_denom_metadata( + self, + sender: str, + description: str, + denom: str, + subdenom: str, + token_decimals: int, + name: str, + symbol: str, + uri: str, + uri_hash: str, + ) -> token_factory_tx_pb.MsgSetDenomMetadata: + micro_denom_unit = bank_pb.DenomUnit( + denom=denom, + exponent=0, + aliases=[f"micro{subdenom}"], + ) + denom_unit = bank_pb.DenomUnit( + denom=subdenom, + exponent=token_decimals, + aliases=[subdenom], + ) + metadata = bank_pb.Metadata( + description=description, + denom_units=[micro_denom_unit, denom_unit], + base=denom, + display=subdenom, + name=name, + symbol=symbol, + uri=uri, + uri_hash=uri_hash, + ) + return token_factory_tx_pb.MsgSetDenomMetadata(sender=sender, metadata=metadata) + + def msg_update_params( + self, + authority: str, + denom: str, + amount: int, + ) -> token_factory_tx_pb.MsgUpdateParams: + coin = self.Coin(amount=amount, denom=denom) + params = token_factory_params_pb.Params( + denom_creation_fee=[coin], + ) + return token_factory_tx_pb.MsgUpdateParams( + authority=authority, + params=params, + ) + + def msg_change_admin( + self, + sender: str, + denom: str, + new_admin: str, + ) -> token_factory_tx_pb.MsgChangeAdmin: + return token_factory_tx_pb.MsgChangeAdmin( + sender=sender, + denom=denom, + new_admin=new_admin, + ) + def chain_stream_bank_balances_filter( self, accounts: Optional[List[str]] = None ) -> chain_stream_query.BankBalancesFilter: diff --git a/tests/test_composer.py b/tests/test_composer.py index f3a9bdc2..392d0e7a 100644 --- a/tests/test_composer.py +++ b/tests/test_composer.py @@ -260,3 +260,119 @@ def test_buy_binary_option_order_creation_without_fixed_denom( assert order.order_type == exchange_pb2.OrderType.BUY assert order.margin == str(int(expected_margin)) assert order.trigger_price == "0" + + def test_msg_create_denom(self, basic_composer: Composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + subdenom = "inj-test" + name = "Injective Test" + symbol = "INJTEST" + + message = basic_composer.msg_create_denom( + sender=sender, + subdenom=subdenom, + name=name, + symbol=symbol, + ) + + assert message.sender == sender + assert message.subdenom == subdenom + assert message.name == name + assert message.symbol == symbol + + def test_msg_mint(self, basic_composer: Composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + amount = basic_composer.Coin( + amount=1_000_000, + denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test", + ) + + message = basic_composer.msg_mint( + sender=sender, + amount=amount, + ) + + assert message.sender == sender + assert message.amount == amount + + def test_msg_burn(self, basic_composer: Composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + amount = basic_composer.Coin( + amount=100, + denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test", + ) + + message = basic_composer.msg_burn( + sender=sender, + amount=amount, + ) + + assert message.sender == sender + assert message.amount == amount + + def test_msg_set_denom_metadata(self, basic_composer: Composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + description = "Injective Test Token" + subdenom = "inj_test" + denom = "factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test" + token_decimals = 6 + name = "Injective Test" + symbol = "INJTEST" + uri = "http://injective-test.com/icon.jpg" + uri_hash = "" + + message = basic_composer.msg_set_denom_metadata( + sender=sender, + description=description, + denom=denom, + subdenom=subdenom, + token_decimals=token_decimals, + name=name, + symbol=symbol, + uri=uri, + uri_hash=uri_hash, + ) + + assert message.sender == sender + assert message.metadata.description == description + assert message.metadata.denom_units[0].denom == denom + assert message.metadata.denom_units[0].exponent == 0 + assert message.metadata.denom_units[0].aliases == [f"micro{subdenom}"] + assert message.metadata.denom_units[1].denom == subdenom + assert message.metadata.denom_units[1].exponent == token_decimals + assert message.metadata.denom_units[1].aliases == [subdenom] + assert message.metadata.base == denom + assert message.metadata.display == subdenom + assert message.metadata.name == name + assert message.metadata.symbol == symbol + assert message.metadata.uri == uri + assert message.metadata.uri_hash == uri_hash + + def test_msg_update_params(self, basic_composer: Composer): + authority = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + amount = 1000 + denom = "factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test" + + message = basic_composer.msg_update_params( + authority=authority, + denom=denom, + amount=amount, + ) + + assert message.authority == authority + assert message.params.denom_creation_fee[0].amount == str(amount) + assert message.params.denom_creation_fee[0].denom == denom + + def test_msg_change_admin(self, basic_composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + denom = "factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test" + new_admin = "inj1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqe2hm49" + + message = basic_composer.msg_change_admin( + sender=sender, + denom=denom, + new_admin=new_admin, + ) + + assert message.sender == sender + assert message.denom == denom + assert message.new_admin == new_admin From 1e359f312e85f7aa9153a8e60cc049c2434d5133 Mon Sep 17 00:00:00 2001 From: abel Date: Thu, 4 Jan 2024 13:01:07 -0300 Subject: [PATCH 7/7] (fix) Removed support for MsgUpdateParams as requested in the PR review --- ...MsgChangeAdmin.py => 75_MsgChangeAdmin.py} | 0 examples/chain_client/75_MsgUpdateParams.py | 37 ------------------- pyinjective/composer.py | 20 +--------- tests/test_composer.py | 15 -------- 4 files changed, 1 insertion(+), 71 deletions(-) rename examples/chain_client/{76_MsgChangeAdmin.py => 75_MsgChangeAdmin.py} (100%) delete mode 100644 examples/chain_client/75_MsgUpdateParams.py diff --git a/examples/chain_client/76_MsgChangeAdmin.py b/examples/chain_client/75_MsgChangeAdmin.py similarity index 100% rename from examples/chain_client/76_MsgChangeAdmin.py rename to examples/chain_client/75_MsgChangeAdmin.py diff --git a/examples/chain_client/75_MsgUpdateParams.py b/examples/chain_client/75_MsgUpdateParams.py deleted file mode 100644 index 0d53231b..00000000 --- a/examples/chain_client/75_MsgUpdateParams.py +++ /dev/null @@ -1,37 +0,0 @@ -import asyncio - -from pyinjective.composer import Composer as ProtoMsgComposer -from pyinjective.core.broadcaster import MsgBroadcasterWithPk -from pyinjective.core.network import Network -from pyinjective.wallet import PrivateKey - - -async def main() -> None: - # select network: local, testnet, mainnet - network = Network.testnet() - composer = ProtoMsgComposer(network=network.string()) - private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3" - - message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( - network=network, - private_key=private_key_in_hexa, - ) - - priv_key = PrivateKey.from_hex(private_key_in_hexa) - pub_key = priv_key.to_public_key() - address = pub_key.to_address() - - message = composer.msg_update_params( - authority=address.to_acc_bech32(), - denom="factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test", - amount=1000, - ) - - # broadcast the transaction - result = await message_broadcaster.broadcast([message]) - print("---Transaction Response---") - print(result) - - -if __name__ == "__main__": - asyncio.get_event_loop().run_until_complete(main()) diff --git a/pyinjective/composer.py b/pyinjective/composer.py index e7d2deff..4c35b07e 100644 --- a/pyinjective/composer.py +++ b/pyinjective/composer.py @@ -28,10 +28,7 @@ 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 from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_query -from pyinjective.proto.injective.tokenfactory.v1beta1 import ( - params_pb2 as token_factory_params_pb, - tx_pb2 as token_factory_tx_pb, -) +from pyinjective.proto.injective.tokenfactory.v1beta1 import tx_pb2 as token_factory_tx_pb REQUEST_TO_RESPONSE_TYPE_MAP = { "MsgCreateSpotLimitOrder": injective_exchange_tx_pb.MsgCreateSpotLimitOrderResponse, @@ -1023,21 +1020,6 @@ def msg_set_denom_metadata( ) return token_factory_tx_pb.MsgSetDenomMetadata(sender=sender, metadata=metadata) - def msg_update_params( - self, - authority: str, - denom: str, - amount: int, - ) -> token_factory_tx_pb.MsgUpdateParams: - coin = self.Coin(amount=amount, denom=denom) - params = token_factory_params_pb.Params( - denom_creation_fee=[coin], - ) - return token_factory_tx_pb.MsgUpdateParams( - authority=authority, - params=params, - ) - def msg_change_admin( self, sender: str, diff --git a/tests/test_composer.py b/tests/test_composer.py index 392d0e7a..05810389 100644 --- a/tests/test_composer.py +++ b/tests/test_composer.py @@ -347,21 +347,6 @@ def test_msg_set_denom_metadata(self, basic_composer: Composer): assert message.metadata.uri == uri assert message.metadata.uri_hash == uri_hash - def test_msg_update_params(self, basic_composer: Composer): - authority = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" - amount = 1000 - denom = "factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test" - - message = basic_composer.msg_update_params( - authority=authority, - denom=denom, - amount=amount, - ) - - assert message.authority == authority - assert message.params.denom_creation_fee[0].amount == str(amount) - assert message.params.denom_creation_fee[0].denom == denom - def test_msg_change_admin(self, basic_composer): sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" denom = "factory/inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r/inj_test"