Coverage for yield_analysis_sdk\subgraph.py: 78%
45 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-02 19:22 +0800
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-02 19:22 +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 }
32 }
33}
34"""
37def _format_vault_addresses(addresses: List[str]) -> List[str]:
38 """Format vault addresses to lowercase for GraphQL compatibility"""
39 return [validate_address_value(addr) for addr in addresses]
42def _send_graphql_query_to_subgraph(
43 chain: Chain,
44 query: str,
45 variables: Dict[str, Any],
46 api_key: str,
47) -> Any:
48 headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
50 # Prepare the request payload
51 payload = {"query": query, "variables": variables}
53 # Send the GraphQL request to the Subgraph
54 response = post(SUBGRAPH_QUERY_URLS[chain], headers=headers, json=payload)
56 # Check if the request was successful
57 if response.status_code == 200:
58 result = response.json()
59 if "errors" in result:
60 raise ConnectionError(f"GraphQL errors: {result['errors']}")
61 else:
62 raise ConnectionError(f"HTTP Error {response.status_code}: {response.text}")
64 return result
67def _format_price_history_response(res: dict) -> List[SharePriceHistory]:
68 if not res or "data" not in res or not res["data"]["vaultStats_collection"]:
69 return []
71 history_by_vault = {}
73 for entry in res["data"]["vaultStats_collection"]:
74 vault_address = entry["vault"]["address"]
75 vault_name = entry["vault"]["name"]
76 timestamp = (
77 int(entry["timestamp"]) // 1000000
78 ) # Convert microseconds to seconds
79 price_per_share = float(entry["pricePerShare"])
81 if vault_address not in history_by_vault:
82 history_by_vault[vault_address] = {
83 "vault_name": vault_name,
84 "vault_address": vault_address,
85 "price_history": [],
86 }
88 history_by_vault[vault_address]["price_history"].append(
89 (timestamp, price_per_share)
90 )
92 # Sort price history by timestamp (oldest first)
93 for vault_address in history_by_vault:
94 history_by_vault[vault_address]["price_history"].sort(key=lambda x: x[0])
96 # Convert to SharePriceHistory objects
97 result = []
98 for vault_data in history_by_vault.values():
99 share_price_history = SharePriceHistory(
100 vault_name=vault_data["vault_name"],
101 vault_address=vault_data["vault_address"],
102 price_history=vault_data["price_history"],
103 )
104 result.append(share_price_history)
106 return result
109def get_daily_share_price_history_from_subgraph(
110 chain: Chain, vault_addresses: List[str], length: int, api_key: str
111) -> List[SharePriceHistory]:
112 """
113 Get the daily share price history from the subgraph for a list of vault addresses.
115 Args:
116 chain: The blockchain chain to query.
117 vault_addresses: A list of vault addresses to query.
118 length: The number of days to query.
119 api_key: The API key for the subgraph.
120 """
121 if not api_key:
122 raise ConfigurationError("SUBGRAPH_API_KEY is required")
124 formatted_addresses = _format_vault_addresses(vault_addresses)
126 variables = {
127 "vault_addresses": formatted_addresses,
128 "length": length * len(formatted_addresses),
129 }
131 res: dict = _send_graphql_query_to_subgraph(
132 chain, daily_share_price_query, variables, api_key
133 )
134 return _format_price_history_response(res)