Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add trade reward campaign endpoint #309

Merged
merged 22 commits into from
Mar 5, 2024

Conversation

aarmoa
Copy link
Collaborator

@aarmoa aarmoa commented Mar 5, 2024

  • Added missing query TradeRewardCampaign from the chain exchange module
  • Synchronized dev branch with the latest changes in master

Summary by CodeRabbit

  • New Features
    • Added support for queries and messages in 'distribution' and 'exchange' modules.
    • Introduced new functionality across various files to enhance querying capabilities, including trade reward campaigns, fee discounts, balance mismatches, market volatility, and more using the Injective Protocol API.
    • New method to fetch trade reward campaign information added to the asynchronous client and ChainGrpcExchangeAPI class.
  • Enhancements
    • Adjusted order quantity and leverage in the derivative market order creation.
  • Bug Fixes
    • Updated cookie assistant logic to better accommodate different server behaviors.
    • Added checks and modified logic for handling missing cookie expiration data.
  • Tests
    • Introduced new test methods and files focusing on trade reward campaigns and network functionality validation.

abel and others added 22 commits February 29, 2024 11:33
…s assistant to tackle the suggestion in the PR review
…Added the use of `dotenv` to load private keys into environment variables. Removed `asyncio` package dependency
…Added the use of `dotenv` to load private keys into environment variables. Removed `asyncio` package dependency
…o new example scripts for all queries and unit tests
…the `distribution` module. Marked as deprecated old functions not following Python naming standards and created the replacements for them (with the correct name following standards)
…new example scripts for all the queries and unit tests.
…er. Added unit tests for new messages. Refactored example scripts to move them into separated subfolders for each module.
…new example scripts for all the queries and unit tests.
…er. Added unit tests for new messages. Refactored example scripts to move them into separated subfolders for each module.
…o feat/add_trade_reward_campaign_endpoint
Copy link
Contributor

coderabbitai bot commented Mar 5, 2024

Walkthrough

The latest updates introduce enhanced query and message support in the distribution and exchange modules, improve private key loading via python-dotenv, and refine cookie assistant logic. Notably, asyncio was removed from dependencies, signaling a shift in handling asynchronous operations. The addition of numerous new files expands the functionality to cover trade rewards, fee discounts, market information, and more, using the Injective Protocol API. These changes aim to bolster the software's capabilities and user experience.

Changes

File(s) Summary
CHANGELOG.md Added support for queries/messages in 'distribution' and 'exchange' modules, improved private key loading, updated cookie logic, removed asyncio.
examples/chain_client/exchange/11_MsgCreateDerivativeMarketOrder.py Adjusted order quantity and leverage, added order_type.
examples/chain_client/exchange/query/41_TradeRewardCampaign.py to 55_MarketAtomicExecutionFeeMultiplier.py Introduced new functionality for querying trade rewards, fee discounts, balance mismatches, market volatility, and more using the Injective Protocol API.
pyinjective/async_client.py, pyinjective/client/chain/grpc/chain_grpc_exchange_api.py Added fetch_trade_reward_campaign method.
pyinjective/core/network.py Enhanced cookie handling in exchange metadata methods.
tests/client/chain/grpc/configurable_exchange_query_servicer.py, tests/client/chain/grpc/test_chain_grpc_exchange_api.py, tests/core/test_network.py Added new test methods and test cases for exchange query servicing and network cookie handling.

🐰✨
In the realm of code where the rabbits hop,
An update arrives, and the carrots stop.
With queries and keys, and cookies so sweet,
The changes they dance, on swift little feet.
So here's to the code, may it run and leap,
With every commit, a promise we keep.
🌟🥕🐇


Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit-tests for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Copy link

codecov bot commented Mar 5, 2024

Codecov Report

Attention: Patch coverage is 81.25000% with 3 lines in your changes are missing coverage. Please review.

Project coverage is 76.16%. Comparing base (a054c55) to head (aa397a0).

Files Patch % Lines
pyinjective/core/network.py 80.00% 1 Missing and 1 partial ⚠️
pyinjective/async_client.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #309      +/-   ##
==========================================
+ Coverage   74.60%   76.16%   +1.55%     
==========================================
  Files          50       50              
  Lines        4099     4111      +12     
  Branches      349      351       +2     
==========================================
+ Hits         3058     3131      +73     
+ Misses        941      875      -66     
- Partials      100      105       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@aarmoa aarmoa merged commit c5645ab into dev Mar 5, 2024
12 checks passed
@aarmoa aarmoa deleted the feat/add_trade_reward_campaign_endpoint branch March 5, 2024 15:59
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 19

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between a054c55 and aa397a0.
Files selected for processing (24)
  • CHANGELOG.md (1 hunks)
  • examples/chain_client/exchange/11_MsgCreateDerivativeMarketOrder.py (1 hunks)
  • examples/chain_client/exchange/query/41_TradeRewardCampaign.py (1 hunks)
  • examples/chain_client/exchange/query/42_FeeDiscountAccountInfo.py (1 hunks)
  • examples/chain_client/exchange/query/43_FeeDiscountSchedule.py (1 hunks)
  • examples/chain_client/exchange/query/44_BalanceMismatches.py (1 hunks)
  • examples/chain_client/exchange/query/45_BalanceWithBalanceHolds.py (1 hunks)
  • examples/chain_client/exchange/query/46_FeeDiscountTierStatistics.py (1 hunks)
  • examples/chain_client/exchange/query/47_MitoVaultInfos.py (1 hunks)
  • examples/chain_client/exchange/query/48_QueryMarketIDFromVault.py (1 hunks)
  • examples/chain_client/exchange/query/49_HistoricalTradeRecords.py (1 hunks)
  • examples/chain_client/exchange/query/50_IsOptedOutOfRewards.py (1 hunks)
  • examples/chain_client/exchange/query/51_OptedOutOfRewardsAccounts.py (1 hunks)
  • examples/chain_client/exchange/query/52_MarketVolatility.py (1 hunks)
  • examples/chain_client/exchange/query/53_BinaryOptionsMarkets.py (1 hunks)
  • examples/chain_client/exchange/query/54_TraderDerivativeConditionalOrders.py (1 hunks)
  • examples/chain_client/exchange/query/55_MarketAtomicExecutionFeeMultiplier.py (1 hunks)
  • poetry.lock (14 hunks)
  • pyinjective/async_client.py (1 hunks)
  • pyinjective/client/chain/grpc/chain_grpc_exchange_api.py (1 hunks)
  • pyinjective/core/network.py (4 hunks)
  • tests/client/chain/grpc/configurable_exchange_query_servicer.py (2 hunks)
  • tests/client/chain/grpc/test_chain_grpc_exchange_api.py (1 hunks)
  • tests/core/test_network.py (1 hunks)
Files not summarized due to errors (1)
  • poetry.lock: Error: Message exceeds token limit
Additional comments: 24
CHANGELOG.md (3)
  • 5-9: The update for version 1.4.0 clearly documents the new features added, including support for queries and messages in the 'distribution' and 'exchange' modules, and the use of python-dotenv for loading private keys. This is a significant enhancement for users who rely on environment variables for configuration.
  • 11-13: The changes in version 1.3.1, which update the cookie assistant logic to accommodate different server behaviors, are well-documented. This change is crucial for ensuring compatibility with various server configurations and enhances the robustness of the SDK.
  • 14-14: The removal of asyncio from dependencies in version 1.3.0 is a significant change that could impact users. It's important to ensure that this change is communicated clearly to users who might be directly importing asyncio in their projects due to its previous inclusion.
Verification successful

The removal of asyncio from dependencies, as noted in the CHANGELOG.md, is a significant change given the extensive use of asyncio throughout the project, including in tests, examples, and core modules. While asyncio is a standard library in Python 3.4 and later, making its explicit inclusion in dependencies unnecessary for running the project, it's crucial that this change is clearly communicated to users. Users need to be aware of the Python version requirements and any potential adjustments they might need to make in their projects due to this change. Ensuring that all documentation reflects this change is essential for minimizing user impact.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Search for mentions of asyncio in example scripts and documentation to ensure users are informed about its removal.
rg --type md --type py 'asyncio'

Length of output: 85922

pyinjective/core/network.py (3)
  • 26-29: The logic for creating metadata when a cookie is available is clear and straightforward. However, it's important to ensure that all callers of exchange_metadata are prepared to handle a None return value, as this could potentially lead to unhandled exceptions if not properly checked.
  • 73-73: Assigning an empty string to cookie_info when it is None is a safe fallback. This ensures that the _exchange_cookie attribute is always a string, which simplifies type checking and handling in other parts of the code. Good approach for consistency.
  • 140-140: The same approach of assigning an empty string to cookie_info when it is None is used here as well. This consistency in handling potential None values across different methods is commendable.
tests/client/chain/grpc/configurable_exchange_query_servicer.py (1)
  • 260-263: The addition of the TradeRewardCampaign method to handle trade reward campaign responses is a necessary update to support the new functionality introduced in the PR. This method follows the established pattern of popping responses from a deque, which is consistent with the other methods in the class. Ensure that the deque is properly managed elsewhere in the code to avoid runtime errors due to popping from an empty deque.
pyinjective/client/chain/grpc/chain_grpc_exchange_api.py (1)
  • 454-458: The method fetch_trade_reward_campaign is correctly implemented following the async pattern used throughout the class. It constructs the appropriate request and executes the gRPC call to fetch trade reward campaign information. This addition aligns with the PR's objective to enhance the chain exchange module's functionality.

However, it's important to ensure that the corresponding gRPC stub method TradeRewardCampaign exists and is correctly implemented on the server side. Additionally, consider adding error handling to manage potential exceptions that could arise from the gRPC call, enhancing the method's robustness.

Verification successful

The search results confirm the existence of the TradeRewardCampaign method within the pyinjective/proto/injective/exchange/v1beta1/query_pb2_grpc.py file, as well as its usage in pyinjective/client/chain/grpc/chain_grpc_exchange_api.py and related test files. This verifies that the method fetch_trade_reward_campaign correctly implements a gRPC call to TradeRewardCampaign, aligning with the review comment's approval of the method's implementation.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify the existence and correct implementation of the gRPC stub method TradeRewardCampaign.
# This script is a placeholder and should be replaced with actual verification steps as needed.
echo "Verify the gRPC stub method TradeRewardCampaign on the server side."

Length of output: 144



Script:

#!/bin/bash
# Search for the gRPC stub file or service definitions containing the TradeRewardCampaign method.
fd 'exchange_query_pb.py' --exec rg 'TradeRewardCampaign'

Length of output: 55



Script:

#!/bin/bash
# Broaden the search for the TradeRewardCampaign method across all Python files.
rg 'TradeRewardCampaign' --type py

Length of output: 39649

tests/client/chain/grpc/test_chain_grpc_exchange_api.py (1)
  • 1912-2023: The test method test_fetch_trade_reward_campaign is well-structured and covers a comprehensive scenario for fetching trade reward campaign information. It properly sets up mock responses and asserts the expected outcome against the actual API call result. However, there are a few areas for improvement and clarification:
  1. Mock Data Completeness: Ensure that the mock data used in the test, especially for complex objects like TradingRewardCampaignBoostInfo, accurately reflects the possible real-world data structures. This includes considering edge cases and varying data configurations.

  2. Assertion Granularity: While the final assertion checks if the entire response matches the expected structure, it might be beneficial to add more granular assertions. This could involve checking individual fields or subsets of the response to pinpoint issues more precisely if the test fails.

  3. Async and Await Usage: The use of async and await is correct, ensuring that the test properly handles asynchronous calls.

  4. Test Coverage: Consider if additional test cases are needed to cover different scenarios, such as error handling, empty responses, or responses with partial data.

  5. Documentation and Comments: While the test method is straightforward, adding comments or documentation about the purpose of the test and the specific aspects it covers could enhance maintainability and clarity for future developers.

Overall, the test method is a valuable addition to the test suite, ensuring the new functionality for fetching trade reward campaign information is verified.

pyinjective/async_client.py (1)
  • 968-970: The newly added method fetch_trade_reward_campaign correctly implements an asynchronous call to self.chain_exchange_api.fetch_trade_reward_campaign(). This addition aligns with the PR's objective of enhancing the chain exchange module by allowing users to query trade reward campaign information. The method is well-structured, follows the async pattern used throughout the file, and returns a dictionary, which is consistent with the return types of similar methods in the class.
poetry.lock (14)
  • 638-694: The update to the coverage package from an unspecified version to 7.4.3 includes a wide range of wheel files for different Python versions and platforms. This update seems to be a routine version bump to keep the package up-to-date. Ensure that this version of coverage is compatible with the project's testing framework and does not introduce any breaking changes.
  • 902-908: The update to the eth-abi package to version 5.0.1 is noted. Given the project's focus on blockchain-related functionalities, it's crucial to verify that this version of eth-abi supports all necessary Ethereum ABI definitions and encoding/decoding operations required by the project. Compatibility with the project's Ethereum interaction code should be confirmed.
  • 917-919: The addition of development, documentation, and testing extras for the eth-abi package suggests an effort to enhance the development environment and testing capabilities. It's important to ensure these extras are utilized in the project's CI/CD pipelines or development setups to leverage their benefits fully.
  • 951-957: The eth-hash package version 0.7.0 update should be checked for compatibility with the project's requirements for Ethereum hashing functionalities. Given the critical role of hashing in blockchain applications, any changes in the hashing behavior or supported algorithms must be thoroughly tested.
  • 972-978: The update to eth-keyfile package version 0.8.0 is crucial for handling Ethereum private keys securely. Ensure that the updated version introduces no regressions in keyfile parsing, generation, or encryption/decryption processes.
  • 1233-1291: The grpcio package update to version 1.62.0 includes a significant number of wheel files for various environments. Given grpcio's role in RPC and networking within the project, it's essential to verify that this update does not affect the stability and performance of network communications, especially in production environments.
  • 1398-1404: The identify package update to version 2.5.35 is primarily used for file identification, which can be important for linting and code formatting tools. Ensure that this update integrates well with the project's code quality tools and does not introduce any issues with file detection.
  • 1788-1794: The pre-commit package update to version 3.6.2 suggests an enhancement in the pre-commit hook framework used by the project. It's important to review the changelog for this version to ensure that all hooks are still compatible and functioning as expected.
  • 1806-1821: The protobuf package update to version 4.25.3 is critical for projects utilizing Protocol Buffers. Compatibility with the project's existing .proto files and generated code should be verified to prevent any serialization or deserialization issues.
  • 1915-1921: The pytest package update to version 8.0.2 is key for maintaining the project's test suite. Ensure that the new version is compatible with the project's test configurations and does not introduce any breaking changes in test discovery or execution.
  • 2270-2373: The addition of the rpds-py package version 0.18.0 introduces Python bindings to Rust's persistent data structures. This addition could significantly impact the project's performance and memory usage patterns. It's crucial to evaluate the integration points of rpds-py within the project to ensure they align with performance goals and do not introduce unexpected behavior.
  • 2388-2400: The setuptools package update to version 69.1.1 is a routine update but essential for the project's build and distribution process. Ensure that this update does not interfere with package building, especially if the project uses specific setuptools features or plugins.
  • 2448-2454: The typing-extensions package update to version 4.10.0 supports backported and experimental type hints. Given the importance of type hints for code quality and maintainability, ensure that the updated version is fully compatible with the project's static type checking setup.
  • 2475-2481: The virtualenv package update to version 20.25.1 is crucial for managing isolated Python environments. Verify that this update does not affect the project's development setup or CI/CD pipelines, especially if virtualenv is used extensively for environment management.

Comment on lines 965 to 973
pending_pool_timestamp=pending_pool_timestamp,
)

async def fetch_trade_reward_campaign(self) -> Dict[str, Any]:
return await self.chain_exchange_api.fetch_trade_reward_campaign()

async def fetch_fee_discount_account_info(self, account: str) -> Dict[str, Any]:
return await self.chain_exchange_api.fetch_fee_discount_account_info(account=account)

Copy link
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-970]

Overall, the AsyncClient class in async_client.py is well-implemented, adhering to best practices for asynchronous programming in Python. The methods are logically structured, and the use of async/await syntax is consistent across the file, which is essential for non-blocking I/O operations. The newly added method fetch_trade_reward_campaign correctly aligns with the PR's objectives and follows the established patterns within the class.

However, a few general recommendations for improvement across the class include:

  • Error Handling: Ensure robust error handling in all network calls and async operations. While some methods include try-except blocks, a more consistent approach across all methods would improve the resilience of the SDK.
  • Documentation: Adding docstrings to methods, especially newly added ones, would enhance the maintainability of the code by providing clear descriptions of method functionalities, expected parameters, and return types.
  • Performance Considerations: For methods making network requests, consider implementing caching mechanisms where appropriate to reduce the number of requests and improve the SDK's performance.

These recommendations aim to further enhance the quality and maintainability of the SDK.

# Example improvement for error handling in async methods
async def fetch_trade_reward_campaign(self) -> Dict[str, Any]:
    try:
        return await self.chain_exchange_api.fetch_trade_reward_campaign()
    except Exception as e:
        # Handle specific exceptions or log errors as appropriate
        raise

Comment on lines +89 to +93
expiration_data: Optional[str] = cookie.get("GCLB", {}).get("expires", None)
if expiration_data is None:
expiration_time = 0
else:
expiration_time = datetime.datetime.strptime(expiration_data, "%a, %d-%b-%Y %H:%M:%S %Z").timestamp()
Copy link
Contributor

Choose a reason for hiding this comment

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

The handling of missing expiration data in _is_cookie_expired by setting expiration_time to 0 is a pragmatic solution. However, it's crucial to document the behavior and implications of this choice, especially how it affects cookie renewal logic. Consider adding a comment explaining why setting expiration_time to 0 is appropriate and how it impacts the session renewal process.

Comment on lines +7 to +19
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()

# initialize grpc client
client = AsyncClient(network)

infos = await client.fetch_mito_vault_infos()
print(infos)


if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Copy link
Contributor

Choose a reason for hiding this comment

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

The example script is well-structured and demonstrates the usage of the fetch_mito_vault_infos method effectively. However, there are a couple of areas that could be improved for robustness and clarity:

  • Consider adding error handling around the async call to fetch_mito_vault_infos to manage potential exceptions that could arise from network issues or API changes.
  • Enhancing the output formatting could provide users with a clearer understanding of the data structure returned by the query, especially for complex data types.
-    infos = await client.fetch_mito_vault_infos()
+    try:
+        infos = await client.fetch_mito_vault_infos()
+        # Consider adding more detailed formatting or processing here
+        print(infos)
+    except Exception as e:
+        print(f"Failed to fetch Mito Vault Infos: {e}")

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
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
infos = await client.fetch_mito_vault_infos()
print(infos)
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
try:
infos = await client.fetch_mito_vault_infos()
# Consider adding more detailed formatting or processing here
print(infos)
except Exception as e:
print(f"Failed to fetch Mito Vault Infos: {e}")
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())

Comment on lines +11 to +31
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)

# 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())

campaign = await client.fetch_trade_reward_campaign()
print(campaign)


if __name__ == "__main__":
Copy link
Contributor

Choose a reason for hiding this comment

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

The example script for querying trade reward campaigns is well-structured and follows the project's conventions for async operations. However, there are a few areas for improvement:

  • Error Handling: Consider adding error handling around the await client.fetch_trade_reward_campaign() call to gracefully handle potential exceptions that could arise from network issues or API changes.
  • Environment Variables: The script assumes the presence of an environment variable INJECTIVE_PRIVATE_KEY. It's good practice to check if the variable is not None after retrieval and provide a meaningful error message if it's missing.
  • Hardcoded Network: The network is hardcoded to testnet. For better flexibility, consider allowing the network type to be passed as an argument or environment variable.
+ if configured_private_key is None:
+     raise ValueError("INJECTIVE_PRIVATE_KEY environment variable is not set.")
+ try:
+     campaign = await client.fetch_trade_reward_campaign()
+     print(campaign)
+ except Exception as e:
+     print(f"Failed to fetch trade reward campaign: {e}")

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
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)
# 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())
campaign = await client.fetch_trade_reward_campaign()
print(campaign)
if __name__ == "__main__":
async def main() -> None:
dotenv.load_dotenv()
configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY")
+ if configured_private_key is None:
+ raise ValueError("INJECTIVE_PRIVATE_KEY environment variable is not set.")
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
# 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())
+ try:
+ campaign = await client.fetch_trade_reward_campaign()
+ print(campaign)
+ except Exception as e:
+ print(f"Failed to fetch trade reward campaign: {e}")
if __name__ == "__main__":

Comment on lines +7 to +19
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()

# initialize grpc client
client = AsyncClient(network)

schedule = await client.fetch_fee_discount_schedule()
print(schedule)


if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to the previous example, this script demonstrates the usage of the fetch_fee_discount_schedule method effectively. To improve robustness and clarity:

  • Implement error handling around the async call to manage potential exceptions gracefully.
  • Enhance the output formatting for a better understanding of the returned data, particularly for users unfamiliar with the data structure.
-    schedule = await client.fetch_fee_discount_schedule()
+    try:
+        schedule = await client.fetch_fee_discount_schedule()
+        # Consider adding more detailed formatting or processing here
+        print(schedule)
+    except Exception as e:
+        print(f"Failed to fetch Fee Discount Schedule: {e}")

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
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
schedule = await client.fetch_fee_discount_schedule()
print(schedule)
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
try:
schedule = await client.fetch_fee_discount_schedule()
# Consider adding more detailed formatting or processing here
print(schedule)
except Exception as e:
print(f"Failed to fetch Fee Discount Schedule: {e}")
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())

Comment on lines +11 to +37
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)

# 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())

subaccount_id = address.get_subaccount_id(index=0)

orders = await client.fetch_trader_derivative_conditional_orders(
subaccount_id=subaccount_id,
market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6",
)
print(orders)


if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Copy link
Contributor

Choose a reason for hiding this comment

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

The script for querying trader derivative conditional orders is well-structured. Recommendations for further improvement include:

  • Error Handling: Implement error handling around the await client.fetch_trader_derivative_conditional_orders(...) call to better manage potential exceptions.
  • Parameterization: The subaccount_id and market_id are derived from the loaded account and hardcoded, respectively. Consider allowing these values to be passed as arguments or read from environment variables for greater flexibility.
+ try:
+     orders = await client.fetch_trader_derivative_conditional_orders(
+         subaccount_id=subaccount_id,
+         market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6",
+     )
+     print(orders)
+ except Exception as e:
+     print(f"Failed to fetch trader derivative conditional orders: {e}")

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
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)
# 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())
subaccount_id = address.get_subaccount_id(index=0)
orders = await client.fetch_trader_derivative_conditional_orders(
subaccount_id=subaccount_id,
market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6",
)
print(orders)
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
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)
# 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())
subaccount_id = address.get_subaccount_id(index=0)
try:
orders = await client.fetch_trader_derivative_conditional_orders(
subaccount_id=subaccount_id,
market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6",
)
print(orders)
except Exception as e:
print(f"Failed to fetch trader derivative conditional orders: {e}")
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())

Comment on lines +46 to +50
quantity=Decimal(0.1),
margin=composer.calculate_margin(
quantity=Decimal(0.01), price=Decimal(50000), leverage=Decimal(3), is_reduce_only=False
quantity=Decimal(0.1), price=Decimal(50000), leverage=Decimal(1), is_reduce_only=False
),
order_type="BUY",
Copy link
Contributor

Choose a reason for hiding this comment

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

The adjustments made to the main function in creating a derivative market order are clear and align with the described objectives. However, consider the following:

  • Validation and Error Handling: Ensure that the values for quantity, price, and leverage are validated before use to prevent invalid transactions. Additionally, consider adding error handling around the transaction simulation and broadcast steps to gracefully handle potential failures.
  • Parameterization: The market_id, fee_recipient, price, quantity, leverage, and order_type are hardcoded. For enhanced usability, consider allowing these values to be passed as arguments or read from environment variables.
+ # Example validation (simplified)
+ if quantity <= 0 or price <= 0 or leverage <= 0:
+     raise ValueError("Quantity, price, and leverage must be greater than 0.")
+ try:
+     # Transaction simulation and broadcast code...
+ except Exception as e:
+     print(f"Failed to create derivative market order: {e}")

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
quantity=Decimal(0.1),
margin=composer.calculate_margin(
quantity=Decimal(0.01), price=Decimal(50000), leverage=Decimal(3), is_reduce_only=False
quantity=Decimal(0.1), price=Decimal(50000), leverage=Decimal(1), is_reduce_only=False
),
order_type="BUY",
quantity=Decimal(0.1),
margin=composer.calculate_margin(
quantity=Decimal(0.1), price=Decimal(50000), leverage=Decimal(1), is_reduce_only=False
),
order_type="BUY",

Comment on lines +6 to +105
class TestBareMetalLoadBalancedCookieAssistant:
@pytest.mark.asyncio
async def test_chain_metadata(self):
assistant = BareMetalLoadBalancedCookieAssistant()
dummy_metadata = [("set-cookie", "expected_cookie")]

async def dummy_metadata_provider():
return dummy_metadata

metadata = await assistant.chain_metadata(metadata_query_provider=dummy_metadata_provider)
expected_metadata = (("cookie", "expected_cookie"),)

assert expected_metadata == metadata

@pytest.mark.asyncio
async def test_chain_metadata_fails_when_cookie_info_not_included_in_server_response(self):
assistant = BareMetalLoadBalancedCookieAssistant()
dummy_metadata = [("invalid_key", "invalid_value")]

async def dummy_metadata_provider():
return dummy_metadata

with pytest.raises(RuntimeError, match=f"Error fetching chain cookie ({dummy_metadata})"):
await assistant.chain_metadata(metadata_query_provider=dummy_metadata_provider)

@pytest.mark.asyncio
async def test_exchange_metadata(self):
assistant = BareMetalLoadBalancedCookieAssistant()
dummy_metadata = [("set-cookie", "expected_cookie")]

async def dummy_metadata_provider():
return dummy_metadata

metadata = await assistant.exchange_metadata(metadata_query_provider=dummy_metadata_provider)
expected_metadata = (("cookie", "expected_cookie"),)

assert expected_metadata == metadata

@pytest.mark.asyncio
async def test_exchange_metadata_is_none_when_cookie_info_not_included_in_server_response(self):
assistant = BareMetalLoadBalancedCookieAssistant()
dummy_metadata = [("invalid_key", "invalid_value")]

async def dummy_metadata_provider():
return dummy_metadata

metadata = await assistant.exchange_metadata(metadata_query_provider=dummy_metadata_provider)

assert metadata is None


class TestKubernetesLoadBalancedCookieAssistant:
@pytest.mark.asyncio
async def test_chain_metadata(self):
assistant = KubernetesLoadBalancedCookieAssistant()
dummy_metadata = [("set-cookie", "expected_cookie")]

async def dummy_metadata_provider():
return dummy_metadata

metadata = await assistant.chain_metadata(metadata_query_provider=dummy_metadata_provider)
expected_metadata = (("cookie", "expected_cookie"),)

assert expected_metadata == metadata

@pytest.mark.asyncio
async def test_chain_metadata_fails_when_cookie_info_not_included_in_server_response(self):
assistant = KubernetesLoadBalancedCookieAssistant()
dummy_metadata = [("invalid_key", "invalid_value")]

async def dummy_metadata_provider():
return dummy_metadata

with pytest.raises(RuntimeError, match=f"Error fetching chain cookie ({dummy_metadata})"):
await assistant.chain_metadata(metadata_query_provider=dummy_metadata_provider)

@pytest.mark.asyncio
async def test_exchange_metadata(self):
assistant = KubernetesLoadBalancedCookieAssistant()
dummy_metadata = [("set-cookie", "expected_cookie")]

async def dummy_metadata_provider():
return dummy_metadata

metadata = await assistant.exchange_metadata(metadata_query_provider=dummy_metadata_provider)
expected_metadata = (("cookie", "expected_cookie"),)

assert expected_metadata == metadata

@pytest.mark.asyncio
async def test_exchange_metadata_is_none_when_cookie_info_not_included_in_server_response(self):
assistant = KubernetesLoadBalancedCookieAssistant()
dummy_metadata = [("invalid_key", "invalid_value")]

async def dummy_metadata_provider():
return dummy_metadata

metadata = await assistant.exchange_metadata(metadata_query_provider=dummy_metadata_provider)

assert metadata is None
Copy link
Contributor

Choose a reason for hiding this comment

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

The tests for BareMetalLoadBalancedCookieAssistant and KubernetesLoadBalancedCookieAssistant are comprehensive and cover both successful scenarios and error handling for missing cookie information. A few suggestions for improvement:

  • Mocking External Calls: Ensure that any external calls made during these tests (e.g., to a metadata provider) are properly mocked to avoid making actual network requests during test execution.
  • Test Coverage: Consider adding tests for additional edge cases, such as handling malformed cookie values or network errors during metadata retrieval.
+ # Example of mocking an external call
+ @mock.patch('path.to.metadata_provider', return_value=dummy_metadata)
+ async def test_chain_metadata_with_mocked_provider(self, mock_metadata_provider):
+     # Test code...

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
class TestBareMetalLoadBalancedCookieAssistant:
@pytest.mark.asyncio
async def test_chain_metadata(self):
assistant = BareMetalLoadBalancedCookieAssistant()
dummy_metadata = [("set-cookie", "expected_cookie")]
async def dummy_metadata_provider():
return dummy_metadata
metadata = await assistant.chain_metadata(metadata_query_provider=dummy_metadata_provider)
expected_metadata = (("cookie", "expected_cookie"),)
assert expected_metadata == metadata
@pytest.mark.asyncio
async def test_chain_metadata_fails_when_cookie_info_not_included_in_server_response(self):
assistant = BareMetalLoadBalancedCookieAssistant()
dummy_metadata = [("invalid_key", "invalid_value")]
async def dummy_metadata_provider():
return dummy_metadata
with pytest.raises(RuntimeError, match=f"Error fetching chain cookie ({dummy_metadata})"):
await assistant.chain_metadata(metadata_query_provider=dummy_metadata_provider)
@pytest.mark.asyncio
async def test_exchange_metadata(self):
assistant = BareMetalLoadBalancedCookieAssistant()
dummy_metadata = [("set-cookie", "expected_cookie")]
async def dummy_metadata_provider():
return dummy_metadata
metadata = await assistant.exchange_metadata(metadata_query_provider=dummy_metadata_provider)
expected_metadata = (("cookie", "expected_cookie"),)
assert expected_metadata == metadata
@pytest.mark.asyncio
async def test_exchange_metadata_is_none_when_cookie_info_not_included_in_server_response(self):
assistant = BareMetalLoadBalancedCookieAssistant()
dummy_metadata = [("invalid_key", "invalid_value")]
async def dummy_metadata_provider():
return dummy_metadata
metadata = await assistant.exchange_metadata(metadata_query_provider=dummy_metadata_provider)
assert metadata is None
class TestKubernetesLoadBalancedCookieAssistant:
@pytest.mark.asyncio
async def test_chain_metadata(self):
assistant = KubernetesLoadBalancedCookieAssistant()
dummy_metadata = [("set-cookie", "expected_cookie")]
async def dummy_metadata_provider():
return dummy_metadata
metadata = await assistant.chain_metadata(metadata_query_provider=dummy_metadata_provider)
expected_metadata = (("cookie", "expected_cookie"),)
assert expected_metadata == metadata
@pytest.mark.asyncio
async def test_chain_metadata_fails_when_cookie_info_not_included_in_server_response(self):
assistant = KubernetesLoadBalancedCookieAssistant()
dummy_metadata = [("invalid_key", "invalid_value")]
async def dummy_metadata_provider():
return dummy_metadata
with pytest.raises(RuntimeError, match=f"Error fetching chain cookie ({dummy_metadata})"):
await assistant.chain_metadata(metadata_query_provider=dummy_metadata_provider)
@pytest.mark.asyncio
async def test_exchange_metadata(self):
assistant = KubernetesLoadBalancedCookieAssistant()
dummy_metadata = [("set-cookie", "expected_cookie")]
async def dummy_metadata_provider():
return dummy_metadata
metadata = await assistant.exchange_metadata(metadata_query_provider=dummy_metadata_provider)
expected_metadata = (("cookie", "expected_cookie"),)
assert expected_metadata == metadata
@pytest.mark.asyncio
async def test_exchange_metadata_is_none_when_cookie_info_not_included_in_server_response(self):
assistant = KubernetesLoadBalancedCookieAssistant()
dummy_metadata = [("invalid_key", "invalid_value")]
async def dummy_metadata_provider():
return dummy_metadata
metadata = await assistant.exchange_metadata(metadata_query_provider=dummy_metadata_provider)
assert metadata is None
# Example of mocking an external call
@mock.patch('path.to.metadata_provider', return_value=dummy_metadata)
async def test_chain_metadata_with_mocked_provider(self, mock_metadata_provider):
# Test code...

Comment on lines +7 to +20
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()

# initialize grpc client
client = AsyncClient(network)

records = await client.fetch_historical_trade_records(
market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
)
print(records)


if __name__ == "__main__":
Copy link
Contributor

Choose a reason for hiding this comment

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

This script, showcasing the fetch_historical_trade_records method, could be improved by:

  • Adding error handling to manage exceptions from the async call effectively.
  • Improving the output formatting to aid users in understanding the returned data, particularly for complex data structures.
-    records = await client.fetch_historical_trade_records(
+    try:
+        records = await client.fetch_historical_trade_records(
+            market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
+        )
+        # Recommend enhancing output formatting for clarity
+        print(records)
+    except Exception as e:
+        print(f"Failed to fetch Historical Trade Records: {e}")

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
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
records = await client.fetch_historical_trade_records(
market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
)
print(records)
if __name__ == "__main__":
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
try:
records = await client.fetch_historical_trade_records(
market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
)
# Recommend enhancing output formatting for clarity
print(records)
except Exception as e:
print(f"Failed to fetch Historical Trade Records: {e}")
if __name__ == "__main__":

Comment on lines +7 to +20
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()

# initialize grpc client
client = AsyncClient(network)

multiplier = await client.fetch_market_atomic_execution_fee_multiplier(
market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6",
)
print(multiplier)


if __name__ == "__main__":
Copy link
Contributor

Choose a reason for hiding this comment

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

In this final example, which introduces the fetch_market_atomic_execution_fee_multiplier method, consider:

  • Implementing error handling to ensure the script can handle exceptions from the async call.
  • Enhancing the output formatting to make the complex data structures more understandable for users.
-    multiplier = await client.fetch_market_atomic_execution_fee_multiplier(
+    try:
+        multiplier = await client.fetch_market_atomic_execution_fee_multiplier(
+            market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6",
+        )
+        # Suggest improving output formatting for data clarity
+        print(multiplier)
+    except Exception as e:
+        print(f"Failed to fetch Market Atomic Execution Fee Multiplier: {e}")

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
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
multiplier = await client.fetch_market_atomic_execution_fee_multiplier(
market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6",
)
print(multiplier)
if __name__ == "__main__":
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
try:
multiplier = await client.fetch_market_atomic_execution_fee_multiplier(
market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6",
)
# Suggest improving output formatting for data clarity
print(multiplier)
except Exception as e:
print(f"Failed to fetch Market Atomic Execution Fee Multiplier: {e}")
if __name__ == "__main__":

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant