Coverage for yield_analysis_sdk\subgraph.py: 79%
47 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-17 20:34 +0800
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-17 20:34 +0800
1from typing import Any, Dict, List
3from requests import post
5from .exceptions import ConfigurationError, ConnectionError
6from .type import Chain, SharePriceHistory
7from .validators import validate_address_value
9SUBGRAPH_QUERY_URLS = {
10 Chain.BASE: "https://gateway.thegraph.com/api/subgraphs/id/46pQKDXgcredBSK9cbGU8qEaPEpEZgQ72hSAkpWnKinJ",
11}
13daily_share_price_query = """
14query DailyPriceHistory($vault_addresses: [Bytes!], $length: Int!) {
15 vaultStats_collection(
16 interval: day
17 orderBy: timestamp
18 orderDirection: desc
19 first: $length
20 where: {
21 vault_: {
22 address_in: $vault_addresses
23 }
24 }
25 ) {
26 timestamp
27 pricePerShare
28 vault {
29 address
30 name
31 decimals
32 }
33 }
34}
35"""
38def _format_vault_addresses(addresses: List[str]) -> List[str]:
39 """Format vault addresses to lowercase for GraphQL compatibility"""
40 return [validate_address_value(addr) for addr in addresses]
43def _send_graphql_query_to_subgraph(
44 chain: Chain,
45 query: str,
46 variables: Dict[str, Any],
47 api_key: str,
48) -> Any:
49 headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
51 # Prepare the request payload
52 payload = {"query": query, "variables": variables}
54 # Send the GraphQL request to the Subgraph
55 response = post(SUBGRAPH_QUERY_URLS[chain], headers=headers, json=payload)
57 # Check if the request was successful
58 if response.status_code == 200:
59 result = response.json()
60 if "errors" in result:
61 raise ConnectionError(f"GraphQL errors: {result['errors']}")
62 else:
63 raise ConnectionError(f"HTTP Error {response.status_code}: {response.text}")
65 return result
68def _format_price_history_response(res: dict, underlying_asset_decimals: int) -> List[SharePriceHistory]:
69 if not res or "data" not in res or not res["data"]["vaultStats_collection"]:
70 return []
72 history_by_vault = {}
74 for entry in res["data"]["vaultStats_collection"]:
75 vault_address = entry["vault"]["address"]
76 vault_name = entry["vault"]["name"]
77 vault_decimals = int(entry["vault"]["decimals"])
78 timestamp = (
79 int(entry["timestamp"]) // 1000000
80 ) # Convert microseconds to seconds
81 decimals_multiplier: float = 10 ** (vault_decimals - underlying_asset_decimals)
82 price_per_share = float(entry["pricePerShare"]) * decimals_multiplier
84 if vault_address not in history_by_vault:
85 history_by_vault[vault_address] = {
86 "vault_name": vault_name,
87 "vault_address": vault_address,
88 "price_history": [],
89 }
91 history_by_vault[vault_address]["price_history"].append(
92 (timestamp, price_per_share)
93 )
95 # Sort price history by timestamp (oldest first)
96 for vault_address in history_by_vault:
97 history_by_vault[vault_address]["price_history"].sort(key=lambda x: x[0])
99 # Convert to SharePriceHistory objects
100 result = []
101 for vault_data in history_by_vault.values():
102 share_price_history = SharePriceHistory(
103 vault_name=vault_data["vault_name"],
104 vault_address=vault_data["vault_address"],
105 price_history=vault_data["price_history"],
106 )
107 result.append(share_price_history)
109 return result
112def get_daily_share_price_history_from_subgraph(
113 chain: Chain, vault_addresses: List[str], underlying_asset_decimals: int, length: int, api_key: str
114) -> List[SharePriceHistory]:
115 """
116 Get the daily share price history from the subgraph for a list of vault addresses.
118 Args:
119 chain: The blockchain chain to query.
120 vault_addresses: A list of vault addresses to query.
121 length: The number of days to query.
122 api_key: The API key for the subgraph.
123 """
124 if not api_key:
125 raise ConfigurationError("SUBGRAPH_API_KEY is required")
127 formatted_addresses = _format_vault_addresses(vault_addresses)
129 variables = {
130 "vault_addresses": formatted_addresses,
131 "length": length * len(formatted_addresses),
132 }
134 res: dict = _send_graphql_query_to_subgraph(
135 chain, daily_share_price_query, variables, api_key
136 )
137 return _format_price_history_response(res, underlying_asset_decimals)