Skip to content

Commit 7f7782e

Browse files
committed
feat: support multiple chain for Compound
1 parent 0d83a02 commit 7f7782e

File tree

2 files changed

+68
-32
lines changed

2 files changed

+68
-32
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# list obtained from: https://github.com/DefiLlama/chainlist/blob/main/constants/chainIds.js
2+
# more chains can be added from: https://chainid.network/chains_mini.json
3+
CHAIN_ID_TO_NETWORK = {
4+
1: "ethereum",
5+
10: "optimism",
6+
137: "polygon",
7+
5000: "mantle",
8+
8453: "base",
9+
42161: "arbitrum",
10+
534352: "scroll",
11+
}

openagent/tools/arbitrum/compound_market_analysis.py

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1+
import asyncio
12
from dataclasses import dataclass
23
from textwrap import dedent
3-
from typing import Optional
4+
from typing import Optional, List
45

5-
import httpx
66
from langchain.chat_models import init_chat_model
77
from langchain.prompts import PromptTemplate
88
from langchain_core.output_parsers import StrOutputParser
99
from loguru import logger
1010
from pydantic import BaseModel, Field
1111
from openagent.agent.config import ModelConfig
12+
from openagent.core.constants.chain_ids import CHAIN_ID_TO_NETWORK
1213
from openagent.core.tool import Tool
1314
from openagent.core.utils.fetch_json import fetch_json
1415

1516

1617
@dataclass
1718
class 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

Comments
 (0)