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

1from typing import Any, Dict, List 

2 

3from requests import post 

4 

5from .exceptions import ConfigurationError, ConnectionError 

6from .type import Chain, SharePriceHistory 

7from .validators import validate_address_value 

8 

9SUBGRAPH_QUERY_URLS = { 

10 Chain.BASE: "https://gateway.thegraph.com/api/subgraphs/id/46pQKDXgcredBSK9cbGU8qEaPEpEZgQ72hSAkpWnKinJ", 

11} 

12 

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

35 

36 

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] 

40 

41 

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}"} 

49 

50 # Prepare the request payload 

51 payload = {"query": query, "variables": variables} 

52 

53 # Send the GraphQL request to the Subgraph 

54 response = post(SUBGRAPH_QUERY_URLS[chain], headers=headers, json=payload) 

55 

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}") 

63 

64 return result 

65 

66 

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 [] 

70 

71 history_by_vault = {} 

72 

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"]) 

80 

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 } 

87 

88 history_by_vault[vault_address]["price_history"].append( 

89 (timestamp, price_per_share) 

90 ) 

91 

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]) 

95 

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) 

105 

106 return result 

107 

108 

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. 

114 

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") 

123 

124 formatted_addresses = _format_vault_addresses(vault_addresses) 

125 

126 variables = { 

127 "vault_addresses": formatted_addresses, 

128 "length": length * len(formatted_addresses), 

129 } 

130 

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)