1+ import asyncio
12from dataclasses import dataclass
23from textwrap import dedent
3- from typing import Optional
4+ from typing import Optional , List
45
5- import httpx
66from langchain .chat_models import init_chat_model
77from langchain .prompts import PromptTemplate
88from langchain_core .output_parsers import StrOutputParser
99from loguru import logger
1010from pydantic import BaseModel , Field
1111from openagent .agent .config import ModelConfig
12+ from openagent .core .constants .chain_ids import CHAIN_ID_TO_NETWORK
1213from openagent .core .tool import Tool
1314from openagent .core .utils .fetch_json import fetch_json
1415
1516
1617@dataclass
1718class CompoundMarketData :
1819 address : str
19- collateralAssets : list [str ]
2020 borrowAPR : float
21- supplyAPR : float
2221 borrowAPRChange24h : float
22+ chain_id : int
23+ supplyAPR : float
2324 supplyAPRChange24h : float
2425
2526
@@ -28,18 +29,23 @@ class CompoundMarketConfig(BaseModel):
2829 default = None ,
2930 description = "Model configuration for this tool. If not provided, will use agent's core model" ,
3031 )
32+ chain_ids : List [int ] = Field (
33+ default_factory = [],
34+ description = "List of chain IDs to fetch Compound market data from" ,
35+ )
3136
3237
33- class ArbitrumCompoundMarketTool (Tool [CompoundMarketConfig ]):
34- def __init__ (self , core_model = None ):
38+ class CompoundMarketTool (Tool [CompoundMarketConfig ]):
39+ def __init__ (self , core_model = None , chain_ids : List [ int ] = [] ):
3540 super ().__init__ ()
41+ self .chain_ids = chain_ids
3642 self .core_model = core_model
3743 self .tool_model = None
3844 self .tool_prompt = None
3945
4046 @property
4147 def name (self ) -> str :
42- return "arbitrum_compound_market_analysis "
48+ return "compound_market_analysis "
4349
4450 @property
4551 def description (self ):
@@ -66,7 +72,6 @@ async def setup(self, config: CompoundMarketConfig) -> None:
6672
6773 ### Data Structure
6874 - Market object with:
69- - `collateralAssets`: List of supported collateral assets
7075 - `borrowAPR`: Current borrow APR
7176 - `supplyAPR`: Current supply APR
7277 - `borrowAPRChange24h`: 24h borrow APR change
@@ -90,16 +95,16 @@ async def __call__(self) -> str:
9095 raise RuntimeError ("Model not initialized" )
9196
9297 try :
93- # Fetch Compound arbitrum-network market data
94- arbitrum_market_list = await self ._fetch_compound_arbitrum_market_data ( )
98+ # Fetch Compound market data
99+ market_list = await self ._fetch_compound_market_data ( self . chain_ids )
95100
96101 # Analyze market data
97102 chain = self .tool_prompt | self .tool_model | StrOutputParser ()
98103
99104 # Run analysis chain
100105 response = await chain .ainvoke (
101106 {
102- "data" : arbitrum_market_list ,
107+ "data" : market_list ,
103108 }
104109 )
105110
@@ -112,50 +117,70 @@ async def __call__(self) -> str:
112117 return f"Error in { self .name } tool: { e } "
113118
114119 @staticmethod
115- async def _fetch_compound_arbitrum_market_data () -> list [CompoundMarketData ]:
120+ async def _fetch_compound_market_data (
121+ chain_ids : List [int ],
122+ ) -> list [CompoundMarketData ]:
116123 results = await fetch_json (
117124 url = "https://v3-api.compound.finance/market/all-networks/all-contracts/summary"
118125 )
119126
120- # Filter for Arbitrum markets (chain_id 42161)
121- arbitrum_markets = [market for market in results if market ["chain_id" ] == 42161 ]
127+ # Filter for chain_ids
128+ markets = [market for market in results if market ["chain_id" ] in chain_ids ]
122129
123- market_data = []
130+ if not markets :
131+ logger .warning ("No markets found for the given chain_ids" )
132+ return []
124133
125- for market in arbitrum_markets :
126- # Fetch historical data for each address
127- historical_data = await fetch_json (
128- f"https://v3-api.compound.finance/market/arbitrum-mainnet/{ market ['comet' ]['address' ]} /historical/summary"
129- )
134+ async def fetch_historical_data (address : str , chain_id : int ) -> dict :
135+ """Fetches historical data for a specific market market_address."""
130136
131- # Sort historical data by timestamp in descending order (newest first)
132- sorted_data = sorted (
133- historical_data , key = lambda x : x ["timestamp" ], reverse = True
137+ network = CHAIN_ID_TO_NETWORK .get (chain_id )
138+ network_path = f"{ network } -" if network != "ethereum" else ""
139+
140+ return await fetch_json (
141+ f"https://v3-api.compound.finance/market/{ network_path } mainnet/{ address } /historical/summary"
134142 )
135143
136- if len (sorted_data ) < 2 :
137- logger .warning (
138- f"Insufficient historical data for { market ['comet' ]['address' ]} "
139- )
144+ historical_data_list = await asyncio .gather (
145+ * [
146+ fetch_historical_data (market ["comet" ]["address" ], market ["chain_id" ])
147+ for market in markets
148+ ]
149+ )
150+
151+ market_data = []
152+
153+ for market , historical_data in zip (markets , historical_data_list ):
154+ market_address = market ["comet" ]["address" ]
155+
156+ if len (historical_data ) < 2 :
157+ logger .warning (f"Insufficient historical data for { market_address } " )
140158 continue
141159
142- # Convert string APRs to float
160+ # Sort historical data by timestamp (descending)
161+ sorted_data = sorted (
162+ historical_data , key = lambda x : x ["timestamp" ], reverse = True
163+ )[:2 ]
164+
165+ # Convert APR values from string to float
166+ # purposely not using get() so it throws an error if the key is missing
167+ # which indicates a data structure change with the API response
143168 current_borrow_apr = float (sorted_data [0 ]["borrow_apr" ])
144169 current_supply_apr = float (sorted_data [0 ]["supply_apr" ])
145170 yesterday_borrow_apr = float (sorted_data [1 ]["borrow_apr" ])
146171 yesterday_supply_apr = float (sorted_data [1 ]["supply_apr" ])
147172
148- # Calculate 24h changes
173+ # Calculate 24h APR changes
149174 borrow_apr_change_24h = current_borrow_apr - yesterday_borrow_apr
150175 supply_apr_change_24h = current_supply_apr - yesterday_supply_apr
151176
152177 market_data .append (
153178 CompoundMarketData (
154- address = market ["comet" ]["address" ],
155- collateralAssets = market ["collateral_asset_symbols" ],
179+ address = market_address ,
156180 borrowAPR = current_borrow_apr ,
157- supplyAPR = current_supply_apr ,
158181 borrowAPRChange24h = borrow_apr_change_24h ,
182+ chain_id = market ["chain_id" ],
183+ supplyAPR = current_supply_apr ,
159184 supplyAPRChange24h = supply_apr_change_24h ,
160185 )
161186 )
0 commit comments