From 40cff4b53dacbee64f92e180b87315aed6fb578d Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 10 Apr 2024 15:32:09 -0300 Subject: [PATCH 1/2] (feat) Added support for IBC transfer module queries and the transfer message. Added unit tests and script examples --- .../ibc/transfer/1_MsgTransfer.py | 61 ++++++ .../ibc/transfer/query/1_DenomTrace.py | 26 +++ .../ibc/transfer/query/2_DenomTraces.py | 22 ++ .../ibc/transfer/query/3_DenomHash.py | 23 +++ .../ibc/transfer/query/4_EscrowAddress.py | 22 ++ .../transfer/query/5_TotalEscrowForDenom.py | 21 ++ pyinjective/async_client.py | 23 +++ pyinjective/composer.py | 38 +++- .../core/tx/grpc/ibc_transfer_grpc_api.py | 58 ++++++ ...onfigurable_ibc_transfer_query_servicer.py | 37 ++++ .../tx/grpc/test_ibc_transfer_grpc_api.py | 191 ++++++++++++++++++ tests/test_composer.py | 40 ++++ 12 files changed, 560 insertions(+), 2 deletions(-) create mode 100644 examples/chain_client/ibc/transfer/1_MsgTransfer.py create mode 100644 examples/chain_client/ibc/transfer/query/1_DenomTrace.py create mode 100644 examples/chain_client/ibc/transfer/query/2_DenomTraces.py create mode 100644 examples/chain_client/ibc/transfer/query/3_DenomHash.py create mode 100644 examples/chain_client/ibc/transfer/query/4_EscrowAddress.py create mode 100644 examples/chain_client/ibc/transfer/query/5_TotalEscrowForDenom.py create mode 100644 pyinjective/core/tx/grpc/ibc_transfer_grpc_api.py create mode 100644 tests/core/tx/grpc/configurable_ibc_transfer_query_servicer.py create mode 100644 tests/core/tx/grpc/test_ibc_transfer_grpc_api.py diff --git a/examples/chain_client/ibc/transfer/1_MsgTransfer.py b/examples/chain_client/ibc/transfer/1_MsgTransfer.py new file mode 100644 index 00000000..604ece6e --- /dev/null +++ b/examples/chain_client/ibc/transfer/1_MsgTransfer.py @@ -0,0 +1,61 @@ +import asyncio +import os +from decimal import Decimal + +import dotenv + +from pyinjective.async_client import AsyncClient +from pyinjective.core.broadcaster import MsgBroadcasterWithPk +from pyinjective.core.network import Network +from pyinjective.wallet import PrivateKey + + +async def main() -> None: + dotenv.load_dotenv() + configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY") + + # select network: local, testnet, mainnet + network = Network.testnet() + + # initialize grpc client + client = AsyncClient(network) + await client.initialize_tokens_from_chain_denoms() + composer = await client.composer() + await client.sync_timeout_height() + + message_broadcaster = MsgBroadcasterWithPk.new_using_simulation( + network=network, + private_key=configured_private_key, + ) + + # load account + priv_key = PrivateKey.from_hex(configured_private_key) + pub_key = priv_key.to_public_key() + address = pub_key.to_address() + await client.fetch_account(address.to_acc_bech32()) + + source_port = "transfer" + source_channel = "channel-126" + token_amount = composer.create_coin_amount(amount=Decimal("0.1"), token_name="INJ") + sender = address.to_acc_bech32() + receiver = "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" + timeout_height = 10 + + # prepare tx msg + message = composer.msg_ibc_transfer( + source_port=source_port, + source_channel=source_channel, + token_amount=token_amount, + sender=sender, + receiver=receiver, + timeout_height=timeout_height, + ) + + # 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/ibc/transfer/query/1_DenomTrace.py b/examples/chain_client/ibc/transfer/query/1_DenomTrace.py new file mode 100644 index 00000000..adb980d0 --- /dev/null +++ b/examples/chain_client/ibc/transfer/query/1_DenomTrace.py @@ -0,0 +1,26 @@ +import asyncio +from hashlib import sha256 + +from google.protobuf import symbol_database + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + + path = "transfer/channel-126" + base_denom = "uluna" + full_path = f"{path}/{base_denom}" + path_hash = sha256(full_path.encode()).hexdigest() + trace_hash = f"ibc/{path_hash}" + + denom_trace = await client.fetch_denom_trace(hash=trace_hash) + print(denom_trace) + + +if __name__ == "__main__": + symbol_db = symbol_database.Default() + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/ibc/transfer/query/2_DenomTraces.py b/examples/chain_client/ibc/transfer/query/2_DenomTraces.py new file mode 100644 index 00000000..8e8d9deb --- /dev/null +++ b/examples/chain_client/ibc/transfer/query/2_DenomTraces.py @@ -0,0 +1,22 @@ +import asyncio + +from google.protobuf import symbol_database + +from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + + pagination = PaginationOption(skip=2, limit=4) + + denom_traces = await client.fetch_denom_traces(pagination=pagination) + print(denom_traces) + + +if __name__ == "__main__": + symbol_db = symbol_database.Default() + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/ibc/transfer/query/3_DenomHash.py b/examples/chain_client/ibc/transfer/query/3_DenomHash.py new file mode 100644 index 00000000..db1db07e --- /dev/null +++ b/examples/chain_client/ibc/transfer/query/3_DenomHash.py @@ -0,0 +1,23 @@ +import asyncio + +from google.protobuf import symbol_database + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + + path = "transfer/channel-126" + base_denom = "uluna" + full_path = f"{path}/{base_denom}" + + denom_hash = await client.fetch_denom_hash(trace=full_path) + print(denom_hash) + + +if __name__ == "__main__": + symbol_db = symbol_database.Default() + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/ibc/transfer/query/4_EscrowAddress.py b/examples/chain_client/ibc/transfer/query/4_EscrowAddress.py new file mode 100644 index 00000000..90d69838 --- /dev/null +++ b/examples/chain_client/ibc/transfer/query/4_EscrowAddress.py @@ -0,0 +1,22 @@ +import asyncio + +from google.protobuf import symbol_database + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + + port_id = "transfer" + channel_id = "channel-126" + + escrow_address = await client.fetch_escrow_address(port_id=port_id, channel_id=channel_id) + print(escrow_address) + + +if __name__ == "__main__": + symbol_db = symbol_database.Default() + asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/chain_client/ibc/transfer/query/5_TotalEscrowForDenom.py b/examples/chain_client/ibc/transfer/query/5_TotalEscrowForDenom.py new file mode 100644 index 00000000..b7b4df68 --- /dev/null +++ b/examples/chain_client/ibc/transfer/query/5_TotalEscrowForDenom.py @@ -0,0 +1,21 @@ +import asyncio + +from google.protobuf import symbol_database + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network + + +async def main() -> None: + network = Network.testnet() + client = AsyncClient(network) + + base_denom = "uluna" + + escrow = await client.fetch_total_escrow_for_denom(denom=base_denom) + print(escrow) + + +if __name__ == "__main__": + symbol_db = symbol_database.Default() + asyncio.get_event_loop().run_until_complete(main()) diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index dc1968e7..b771a65e 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -39,6 +39,7 @@ from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket from pyinjective.core.network import Network from pyinjective.core.token import Token +from pyinjective.core.tx.grpc.ibc_transfer_grpc_api import IBCTransferGrpcApi from pyinjective.core.tx.grpc.tendermint_grpc_api import TendermintGrpcApi from pyinjective.core.tx.grpc.tx_grpc_api import TxGrpcApi from pyinjective.exceptions import NotFoundError @@ -184,6 +185,10 @@ def __init__( channel=self.chain_channel, cookie_assistant=network.chain_cookie_assistant, ) + self.ibc_transfer_api = IBCTransferGrpcApi( + channel=self.chain_channel, + cookie_assistant=network.chain_cookie_assistant, + ) self.tendermint_api = TendermintGrpcApi( channel=self.chain_channel, cookie_assistant=network.chain_cookie_assistant, @@ -3013,6 +3018,24 @@ async def listen_chain_stream_updates( oracle_price_filter=oracle_price_filter, ) + # region IBC Apps module + async def fetch_denom_trace(self, hash: str) -> Dict[str, Any]: + return await self.ibc_transfer_api.fetch_denom_trace(hash=hash) + + async def fetch_denom_traces(self, pagination: Optional[PaginationOption] = None) -> Dict[str, Any]: + return await self.ibc_transfer_api.fetch_denom_traces(pagination=pagination) + + async def fetch_denom_hash(self, trace: str) -> Dict[str, Any]: + return await self.ibc_transfer_api.fetch_denom_hash(trace=trace) + + async def fetch_escrow_address(self, port_id: str, channel_id: str) -> Dict[str, Any]: + return await self.ibc_transfer_api.fetch_escrow_address(port_id=port_id, channel_id=channel_id) + + async def fetch_total_escrow_for_denom(self, denom: str) -> Dict[str, Any]: + return await self.ibc_transfer_api.fetch_total_escrow_for_denom(denom=denom) + + # endregion + async def composer(self): return Composer( network=self.network.string(), diff --git a/pyinjective/composer.py b/pyinjective/composer.py index 9a0807c9..dff94363 100644 --- a/pyinjective/composer.py +++ b/pyinjective/composer.py @@ -22,6 +22,8 @@ from pyinjective.proto.cosmos.staking.v1beta1 import tx_pb2 as cosmos_staking_tx_pb from pyinjective.proto.cosmwasm.wasm.v1 import tx_pb2 as wasm_tx_pb from pyinjective.proto.exchange import injective_explorer_rpc_pb2 as explorer_pb2 +from pyinjective.proto.ibc.applications.transfer.v1 import tx_pb2 as ibc_transfer_tx_pb +from pyinjective.proto.ibc.core.client.v1 import client_pb2 as ibc_core_client_pb from pyinjective.proto.injective.auction.v1beta1 import tx_pb2 as injective_auction_tx_pb from pyinjective.proto.injective.exchange.v1beta1 import ( authz_pb2 as injective_authz_pb, @@ -143,14 +145,14 @@ def Coin(self, amount: int, denom: str): warn("This method is deprecated. Use coin instead", DeprecationWarning, stacklevel=2) return base_coin_pb.Coin(amount=str(amount), denom=denom) - def coin(self, amount: int, denom: str): + def coin(self, amount: int, denom: str) -> base_coin_pb.Coin: """ This method create an instance of Coin gRPC type, considering the amount is already expressed in chain format """ formatted_amount_string = str(int(amount)) return base_coin_pb.Coin(amount=formatted_amount_string, denom=denom) - def create_coin_amount(self, amount: Decimal, token_name: str): + def create_coin_amount(self, amount: Decimal, token_name: str) -> base_coin_pb.Coin: """ This method create an instance of Coin gRPC type, considering the amount is already expressed in chain format """ @@ -2142,6 +2144,38 @@ def msg_update_distribution_params(self, authority: str, community_tax: str, wit def msg_community_pool_spend(self, authority: str, recipient: str, amount: List[base_coin_pb.Coin]): return cosmos_distribution_tx_pb.MsgCommunityPoolSpend(authority=authority, recipient=recipient, amount=amount) + # region IBC Transfer module + def msg_ibc_transfer( + self, + source_port: str, + source_channel: str, + token_amount: base_coin_pb.Coin, + sender: str, + receiver: str, + timeout_height: Optional[int] = None, + timeout_timestamp: Optional[int] = None, + memo: Optional[str] = None, + ) -> ibc_transfer_tx_pb.MsgTransfer: + if timeout_height is None and timeout_timestamp is None: + raise ValueError("IBC Transfer error: Either timeout_height or timeout_timestamp must be provided") + parsed_timeout_height = None + if timeout_height: + parsed_timeout_height = ibc_core_client_pb.Height( + revision_number=timeout_height, revision_height=timeout_height + ) + return ibc_transfer_tx_pb.MsgTransfer( + source_port=source_port, + source_channel=source_channel, + token=token_amount, + sender=sender, + receiver=receiver, + timeout_height=parsed_timeout_height, + timeout_timestamp=timeout_timestamp, + memo=memo, + ) + + # endregion + # data field format: [request-msg-header][raw-byte-msg-response] # you need to figure out this magic prefix number to trim request-msg-header off the data # this method handles only exchange responses diff --git a/pyinjective/core/tx/grpc/ibc_transfer_grpc_api.py b/pyinjective/core/tx/grpc/ibc_transfer_grpc_api.py new file mode 100644 index 00000000..c2e5fa38 --- /dev/null +++ b/pyinjective/core/tx/grpc/ibc_transfer_grpc_api.py @@ -0,0 +1,58 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import CookieAssistant +from pyinjective.proto.ibc.applications.transfer.v1 import ( + query_pb2 as ibc_transfer_query, + query_pb2_grpc as ibc_transfer_query_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IBCTransferGrpcApi: + def __init__(self, channel: Channel, cookie_assistant: CookieAssistant): + self._stub = ibc_transfer_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(cookie_assistant=cookie_assistant) + + async def fetch_params(self) -> Dict[str, Any]: + request = ibc_transfer_query.QueryParamsRequest() + response = await self._execute_call(call=self._stub.Params, request=request) + + return response + + async def fetch_denom_trace(self, hash: str) -> Dict[str, Any]: + request = ibc_transfer_query.QueryDenomTraceRequest(hash=hash) + response = await self._execute_call(call=self._stub.DenomTrace, request=request) + + return response + + async def fetch_denom_traces(self, pagination: Optional[PaginationOption] = None) -> Dict[str, Any]: + if pagination is None: + pagination = PaginationOption() + request = ibc_transfer_query.QueryDenomTracesRequest(pagination=pagination.create_pagination_request()) + response = await self._execute_call(call=self._stub.DenomTraces, request=request) + + return response + + async def fetch_denom_hash(self, trace: str) -> Dict[str, Any]: + request = ibc_transfer_query.QueryDenomHashRequest(trace=trace) + response = await self._execute_call(call=self._stub.DenomHash, request=request) + + return response + + async def fetch_escrow_address(self, port_id: str, channel_id: str) -> Dict[str, Any]: + request = ibc_transfer_query.QueryEscrowAddressRequest(port_id=port_id, channel_id=channel_id) + response = await self._execute_call(call=self._stub.EscrowAddress, request=request) + + return response + + async def fetch_total_escrow_for_denom(self, denom: str) -> Dict[str, Any]: + request = ibc_transfer_query.QueryTotalEscrowForDenomRequest(denom=denom) + response = await self._execute_call(call=self._stub.TotalEscrowForDenom, 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/core/tx/grpc/configurable_ibc_transfer_query_servicer.py b/tests/core/tx/grpc/configurable_ibc_transfer_query_servicer.py new file mode 100644 index 00000000..70a176c6 --- /dev/null +++ b/tests/core/tx/grpc/configurable_ibc_transfer_query_servicer.py @@ -0,0 +1,37 @@ +from collections import deque + +from pyinjective.proto.ibc.applications.transfer.v1 import ( + query_pb2 as ibc_transfer_query, + query_pb2_grpc as ibc_transfer_query_grpc, +) + + +class ConfigurableIBCTransferQueryServicer(ibc_transfer_query_grpc.QueryServicer): + def __init__(self): + super().__init__() + self.denom_trace_responses = deque() + self.denom_traces_responses = deque() + self.params_responses = deque() + self.denom_hash_responses = deque() + self.escrow_address_responses = deque() + self.total_escrow_for_denom_responses = deque() + + async def DenomTrace(self, request: ibc_transfer_query.QueryDenomTraceRequest, context=None, metadata=None): + return self.denom_trace_responses.pop() + + async def DenomTraces(self, request: ibc_transfer_query.QueryDenomTracesRequest, context=None, metadata=None): + return self.denom_traces_responses.pop() + + async def Params(self, request: ibc_transfer_query.QueryParamsRequest, context=None, metadata=None): + return self.params_responses.pop() + + async def DenomHash(self, request: ibc_transfer_query.QueryDenomHashRequest, context=None, metadata=None): + return self.denom_hash_responses.pop() + + async def EscrowAddress(self, request: ibc_transfer_query.QueryEscrowAddressRequest, context=None, metadata=None): + return self.escrow_address_responses.pop() + + async def TotalEscrowForDenom( + self, request: ibc_transfer_query.QueryTotalEscrowForDenomRequest, context=None, metadata=None + ): + return self.total_escrow_for_denom_responses.pop() diff --git a/tests/core/tx/grpc/test_ibc_transfer_grpc_api.py b/tests/core/tx/grpc/test_ibc_transfer_grpc_api.py new file mode 100644 index 00000000..d23e3483 --- /dev/null +++ b/tests/core/tx/grpc/test_ibc_transfer_grpc_api.py @@ -0,0 +1,191 @@ +import base64 + +import grpc +import pytest + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import DisabledCookieAssistant, Network +from pyinjective.core.tx.grpc.ibc_transfer_grpc_api import IBCTransferGrpcApi +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.ibc.applications.transfer.v1 import query_pb2 as ibc_transfer_query, transfer_pb2 as ibc_transfer +from tests.core.tx.grpc.configurable_ibc_transfer_query_servicer import ConfigurableIBCTransferQueryServicer + + +@pytest.fixture +def ibc_transfer_servicer(): + return ConfigurableIBCTransferQueryServicer() + + +class TestTxGrpcApi: + @pytest.mark.asyncio + async def test_fetch_params( + self, + ibc_transfer_servicer, + ): + params = ibc_transfer.Params( + send_enabled=True, + receive_enabled=False, + ) + + ibc_transfer_servicer.params_responses.append( + ibc_transfer_query.QueryParamsResponse( + params=params, + ) + ) + + api = self._api_instance(servicer=ibc_transfer_servicer) + + result_params = await api.fetch_params() + expected_params = {"params": {"sendEnabled": params.send_enabled, "receiveEnabled": params.receive_enabled}} + + assert result_params == expected_params + + @pytest.mark.asyncio + async def test_fetch_denom_trace( + self, + ibc_transfer_servicer, + ): + denom_trace = ibc_transfer.DenomTrace( + path="transfer/channel-126", + base_denom="uluna", + ) + + ibc_transfer_servicer.denom_trace_responses.append( + ibc_transfer_query.QueryDenomTraceResponse( + denom_trace=denom_trace, + ) + ) + + api = self._api_instance(servicer=ibc_transfer_servicer) + + result_trace = await api.fetch_denom_trace(hash=denom_trace.base_denom) + expected_trace = { + "denomTrace": { + "path": denom_trace.path, + "baseDenom": denom_trace.base_denom, + } + } + + assert result_trace == expected_trace + + @pytest.mark.asyncio + async def test_fetch_denom_traces( + self, + ibc_transfer_servicer, + ): + denom_trace = ibc_transfer.DenomTrace( + path="transfer/channel-126", + base_denom="uluna", + ) + result_pagination = pagination_pb.PageResponse( + next_key=b"\001\032\264\007Z\224$]\377s8\343\004-\265\267\314?B\341", + total=16036, + ) + + ibc_transfer_servicer.denom_traces_responses.append( + ibc_transfer_query.QueryDenomTracesResponse( + denom_traces=[denom_trace], + pagination=result_pagination, + ) + ) + + api = self._api_instance(servicer=ibc_transfer_servicer) + + pagination_option = PaginationOption( + skip=10, + limit=30, + reverse=False, + count_total=True, + ) + + result_traces = await api.fetch_denom_traces(pagination=pagination_option) + expected_traces = { + "denomTraces": [ + { + "path": denom_trace.path, + "baseDenom": denom_trace.base_denom, + } + ], + "pagination": { + "nextKey": base64.b64encode(result_pagination.next_key).decode(), + "total": str(result_pagination.total), + }, + } + + assert result_traces == expected_traces + + @pytest.mark.asyncio + async def test_fetch_denom_hash( + self, + ibc_transfer_servicer, + ): + denom_hash = "97498452BF27CC90656FD7D6EFDA287FA2BFFFF3E84691C84CB9E0451F6DF0A4" + + ibc_transfer_servicer.denom_hash_responses.append( + ibc_transfer_query.QueryDenomHashResponse( + hash=denom_hash, + ) + ) + + api = self._api_instance(servicer=ibc_transfer_servicer) + + result_hash = await api.fetch_denom_hash(trace="transfer/channel-126/uluna") + expected_hash = {"hash": denom_hash} + + assert result_hash == expected_hash + + @pytest.mark.asyncio + async def test_fetch_escrow_address( + self, + ibc_transfer_servicer, + ): + address = "inj1w8ent9jwwqy2d5s8grq6muk2hqa6kj2863m3mg" + + ibc_transfer_servicer.escrow_address_responses.append( + ibc_transfer_query.QueryEscrowAddressResponse( + escrow_address=address, + ) + ) + + api = self._api_instance(servicer=ibc_transfer_servicer) + + result_escrow = await api.fetch_escrow_address(port_id="port", channel_id="channel") + expected_escrow = {"escrowAddress": address} + + assert result_escrow == expected_escrow + + @pytest.mark.asyncio + async def test_fetch_total_escrow_for_denom( + self, + ibc_transfer_servicer, + ): + coin = coin_pb.Coin(denom="inj", amount="988987297011197594664") + + ibc_transfer_servicer.total_escrow_for_denom_responses.append( + ibc_transfer_query.QueryTotalEscrowForDenomResponse( + amount=coin, + ) + ) + + api = self._api_instance(servicer=ibc_transfer_servicer) + + result_escrow = await api.fetch_total_escrow_for_denom(denom="inj") + expected_escrow = { + "amount": { + "denom": coin.denom, + "amount": coin.amount, + } + } + + assert result_escrow == expected_escrow + + def _api_instance(self, servicer): + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + cookie_assistant = DisabledCookieAssistant() + + api = IBCTransferGrpcApi(channel=channel, cookie_assistant=cookie_assistant) + api._stub = servicer + + return api diff --git a/tests/test_composer.py b/tests/test_composer.py index 2dcbd056..38a47dce 100644 --- a/tests/test_composer.py +++ b/tests/test_composer.py @@ -1430,3 +1430,43 @@ def test_msg_admin_update_binary_options_market(self, basic_composer): including_default_value_fields=True, ) assert dict_message == expected_message + + def test_msg_ibc_transfer(self, basic_composer): + sender = "inj1apmvarl2xyv6kecx2ukkeymddw3we4zkygjyc0" + source_port = "transfer" + source_channel = "channel-126" + token_amount = basic_composer.create_coin_amount(amount=Decimal("0.1"), token_name="INJ") + receiver = "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" + timeout_height = 10 + timeout_timestamp = 1630000000 + memo = "memo" + + message = basic_composer.msg_ibc_transfer( + source_port=source_port, + source_channel=source_channel, + token_amount=token_amount, + sender=sender, + receiver=receiver, + timeout_height=timeout_height, + timeout_timestamp=timeout_timestamp, + memo=memo, + ) + + expected_message = { + "sourcePort": source_port, + "sourceChannel": source_channel, + "token": { + "amount": "100000000000000000", + "denom": "inj", + }, + "sender": sender, + "receiver": receiver, + "timeoutHeight": {"revisionNumber": str(timeout_height), "revisionHeight": str(timeout_height)}, + "timeoutTimestamp": str(timeout_timestamp), + "memo": memo, + } + dict_message = json_format.MessageToDict( + message=message, + including_default_value_fields=True, + ) + assert dict_message == expected_message From 3d9761d37457d9ebddb18c31dbef9679d28e2309 Mon Sep 17 00:00:00 2001 From: abel Date: Wed, 10 Apr 2024 15:43:22 -0300 Subject: [PATCH 2/2] (fix) Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cecfcd5..f633a392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## [1.5.0] - 9999-99-99 ### Added - Added support for all queries in the chain 'tendermint' module +- Added support for all queries in the IBC Transfer module ### Changed - Refactored cookies management logic to use all gRPC calls' responses to update the current cookies