Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove duplicate word in the changelog entry.

There's a minor typographical issue with the repeated word "Added" in the entry for the IBC Transfer module support. Consider revising for clarity and conciseness.

- - Added support for all queries in the IBC Transfer module
+ - Support for all queries in the IBC Transfer module

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
- Added support for all queries in the IBC Transfer module
- 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
Comment on lines 5 to 11

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 NOTE
This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [1-7]

Clarify the release date for version 1.5.0.

The release date for version 1.5.0 is marked as 9999-99-99, which seems to be a placeholder. Please update this with the actual release date to maintain an accurate and informative changelog.

Expand Down
61 changes: 61 additions & 0 deletions examples/chain_client/ibc/transfer/1_MsgTransfer.py
Original file line number Diff line number Diff line change
@@ -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())
26 changes: 26 additions & 0 deletions examples/chain_client/ibc/transfer/query/1_DenomTrace.py
Original file line number Diff line number Diff line change
@@ -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())
22 changes: 22 additions & 0 deletions examples/chain_client/ibc/transfer/query/2_DenomTraces.py
Original file line number Diff line number Diff line change
@@ -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())
23 changes: 23 additions & 0 deletions examples/chain_client/ibc/transfer/query/3_DenomHash.py
Original file line number Diff line number Diff line change
@@ -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())
22 changes: 22 additions & 0 deletions examples/chain_client/ibc/transfer/query/4_EscrowAddress.py
Original file line number Diff line number Diff line change
@@ -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())
21 changes: 21 additions & 0 deletions examples/chain_client/ibc/transfer/query/5_TotalEscrowForDenom.py
Original file line number Diff line number Diff line change
@@ -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())
23 changes: 23 additions & 0 deletions pyinjective/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down
38 changes: 36 additions & 2 deletions pyinjective/composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +25 to +26

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider grouping imports from the same module.

- 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.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

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
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.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,
Expand Down Expand Up @@ -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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure return type annotation consistency.

The coin method lacks a return type annotation, which is present in other newly added methods like create_coin_amount. For consistency and clarity, consider adding a return type annotation.

- def coin(self, amount: int, denom: str):
+ def coin(self, amount: int, denom: str) -> base_coin_pb.Coin:

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
def coin(self, amount: int, denom: str) -> base_coin_pb.Coin:
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
"""
Expand Down Expand Up @@ -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,
)
Comment on lines +2148 to +2175

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate timeout_height and timeout_timestamp parameters in msg_ibc_transfer.

The method msg_ibc_transfer checks if both timeout_height and timeout_timestamp are None but does not validate the scenario where both are provided, which might not be intended or supported by the underlying IBC protocol. Consider adding a validation to ensure only one of them is provided, or clearly document the expected behavior if both are provided.

+ if timeout_height is not None and timeout_timestamp is not None:
+     raise ValueError("IBC Transfer error: Only one of timeout_height or timeout_timestamp should be provided")

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
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,
)
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")
if timeout_height is not None and timeout_timestamp is not None:
raise ValueError("IBC Transfer error: Only one of timeout_height or timeout_timestamp should 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
Expand Down
58 changes: 58 additions & 0 deletions pyinjective/core/tx/grpc/ibc_transfer_grpc_api.py
Original file line number Diff line number Diff line change
@@ -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)
37 changes: 37 additions & 0 deletions tests/core/tx/grpc/configurable_ibc_transfer_query_servicer.py
Original file line number Diff line number Diff line change
@@ -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()
Loading