From cc74aaf4e1b724a17f201259593be4e71cfb2637 Mon Sep 17 00:00:00 2001 From: 0xSpuddy Date: Wed, 2 Oct 2024 22:31:40 -0400 Subject: [PATCH 1/5] aerodrome working --- scripts/aerodrome.py | 95 ++++++++++++++++++++++++ src/telliot_feeds/sources/leth_source.py | 2 - 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 scripts/aerodrome.py diff --git a/scripts/aerodrome.py b/scripts/aerodrome.py new file mode 100644 index 00000000..fc4420fa --- /dev/null +++ b/scripts/aerodrome.py @@ -0,0 +1,95 @@ +from dataclasses import dataclass +from dataclasses import field +from typing import Any +from typing import Optional + +from telliot_core.apps.telliot_config import TelliotConfig + +from telliot_feeds.dtypes.datapoint import OptionalDataPoint +from telliot_feeds.pricing.price_service import WebPriceService +from telliot_feeds.pricing.price_source import PriceSource +from telliot_feeds.sources.price.spot.coingecko import CoinGeckoSpotPriceSource +from telliot_feeds.sources.price.spot.coinpaprika import CoinpaprikaSpotPriceSource +from telliot_feeds.sources.price.spot.curvefiprice import CurveFiUSDPriceSource +from telliot_feeds.sources.price.spot.uniswapV3 import UniswapV3PriceSource +from telliot_feeds.sources.price_aggregator import PriceAggregator +from telliot_feeds.utils.log import get_logger + +logger = get_logger(__name__) + +# 2. Set the contract address and ABI +contract_address = "0xee717411f6E44F9feE011835C8E6FAaC5dEfF166" +contract_abi = [ + { + "inputs": [ + { + "internalType": "uint8", + "name": "src_len", + "type": "uint8" + }, + { + "internalType": "contract IERC20Metadata[]", + "name": "connectors", + "type": "address[]" + } + ], + "name": "getManyRatesWithConnectors", + "outputs": [ + { + "internalType": "uint256[]", + "name": "rates", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + } +] + +# Function to query the contract +class aerodromeSpotPriceService(WebPriceService): + """Custom sFRAX Price Service""" + + def __init__(self, **kwargs: Any) -> None: + kwargs["name"] = "Custom sFRAX Price Service" + kwargs["url"] = "" + super().__init__(**kwargs) + self.cfg = TelliotConfig() + + def query_contract(src_len, connectors): + # get endpoint + endpoint = self.cfg.endpoints.find(chain_id=8453) + if not endpoint: + logger.error("Endpoint not found for mainnet to get sfrax_eth_ratio") + return None + ep = endpoint[0] + if not ep.connect(): + logger.error("Unable to connect endpoint for mainnet to get sfrax_eth_ratio") + return None + w3 = ep.web3 + contract = w3.eth.contract(address=contract_address, abi=contract_abi) + try: + # Get the contract function + contract_function = contract.functions.getManyRatesWithConnectors(src_len, connectors) + # Call the function and return the result + return contract_function.call() + except Exception as e: + print(f"Error querying contract: {e}") + return None + +@dataclass +class aerodromeSpotPriceSource(PriceSource): + asset: asset = "" + currency: currency = "" + service: aerodromeSpotPriceService = field(default_factory=aerodromeSpotPriceService, init=False) + + +if __name__ == "__main__": + import asyncio + + async def main() -> None: + source = aerodromeSpotPriceSource(src_len=1, connectors=[0xDBFeFD2e8460a6Ee4955A68582F85708BAEA60A3, 0x4200000000000000000000000000000000000006, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913]) + v, _ = await source.fetch_new_datapoint() + print(v) + + asyncio.run(main()) diff --git a/src/telliot_feeds/sources/leth_source.py b/src/telliot_feeds/sources/leth_source.py index 958d168b..85c5c2d0 100644 --- a/src/telliot_feeds/sources/leth_source.py +++ b/src/telliot_feeds/sources/leth_source.py @@ -8,7 +8,6 @@ from telliot_feeds.dtypes.datapoint import OptionalDataPoint from telliot_feeds.pricing.price_service import WebPriceService from telliot_feeds.pricing.price_source import PriceSource -from telliot_feeds.sources.price.spot.coinbase import CoinbaseSpotPriceSource from telliot_feeds.sources.price.spot.coingecko import CoinGeckoSpotPriceSource from telliot_feeds.sources.price.spot.gemini import GeminiSpotPriceSource from telliot_feeds.sources.price.spot.kraken import KrakenSpotPriceSource @@ -99,7 +98,6 @@ async def get_price(self, asset: str, currency: str) -> OptionalDataPoint[float] algorithm="median", sources=[ CoinGeckoSpotPriceSource(asset="eth", currency="usd"), - CoinbaseSpotPriceSource(asset="eth", currency="usd"), GeminiSpotPriceSource(asset="eth", currency="usd"), KrakenSpotPriceSource(asset="eth", currency="usd"), ], From ffad9d36e0bbe80c5855c4070ba571e650a51b60 Mon Sep 17 00:00:00 2001 From: 0xSpuddy Date: Thu, 3 Oct 2024 09:42:40 -0400 Subject: [PATCH 2/5] refactor with abi instead of calldata --- .../feeds/superoethb_eth_feed.py | 2 + .../sources/superoethb_source.py | 144 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 src/telliot_feeds/sources/superoethb_source.py diff --git a/src/telliot_feeds/feeds/superoethb_eth_feed.py b/src/telliot_feeds/feeds/superoethb_eth_feed.py index fdfab4d4..be83ad1b 100644 --- a/src/telliot_feeds/feeds/superoethb_eth_feed.py +++ b/src/telliot_feeds/feeds/superoethb_eth_feed.py @@ -2,6 +2,7 @@ from telliot_feeds.queries.price.spot_price import SpotPrice from telliot_feeds.sources.price.spot.coingecko import CoinGeckoSpotPriceSource from telliot_feeds.sources.price_aggregator import PriceAggregator +from telliot_feeds.sources.superoethb_source import superoethbSpotPriceSource superoethb_eth_median_feed = DataFeed( @@ -12,6 +13,7 @@ algorithm="median", sources=[ CoinGeckoSpotPriceSource(asset="superoethb", currency="eth"), + superoethbSpotPriceSource(asset="superoethb", currency="eth"), ], ), ) diff --git a/src/telliot_feeds/sources/superoethb_source.py b/src/telliot_feeds/sources/superoethb_source.py new file mode 100644 index 00000000..3b28149b --- /dev/null +++ b/src/telliot_feeds/sources/superoethb_source.py @@ -0,0 +1,144 @@ +from dataclasses import dataclass +from dataclasses import field +from typing import Any +from typing import Optional +from typing import List + +from telliot_core.apps.telliot_config import TelliotConfig + +from telliot_feeds.dtypes.datapoint import datetime_now_utc +from telliot_feeds.feeds.eth_usd_feed import eth_usd_median_feed +from telliot_feeds.dtypes.datapoint import OptionalDataPoint +from telliot_feeds.pricing.price_service import WebPriceService +from telliot_feeds.pricing.price_source import PriceSource +from telliot_feeds.utils.log import get_logger + +logger = get_logger(__name__) + +AERODROME_CONTRACT = "0xee717411f6E44F9feE011835C8E6FAaC5dEfF166" +CONTRACT_ABI = [ + { + "inputs": [ + { + "internalType": "uint8", + "name": "src_len", + "type": "uint8" + }, + { + "internalType": "contract IERC20Metadata[]", + "name": "connectors", + "type": "address[]" + } + ], + "name": "getManyRatesWithConnectors", + "outputs": [ + { + "internalType": "uint256[]", + "name": "rates", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + } +] +SRC_LEN = 1 +CONNECTORS = [ + "0xDBFeFD2e8460a6Ee4955A68582F85708BAEA60A3", + "0x4200000000000000000000000000000000000006", + "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" +] + + +class superOETHbSpotPriceService(WebPriceService): + """Custom superOETHb Price Service""" + + def __init__(self, **kwargs: Any) -> None: + kwargs["name"] = "Custom superOETHb Price Service" + kwargs["url"] = "" + super().__init__(**kwargs) + self.cfg = TelliotConfig() + self.contract_address: Optional[str] = None + self.contract_abi: Optional[Any] = None + self.src_len: Optional[int] = None + self.connectors: Optional[List[Any]] = None + + def get_aerodrome_usd_price(self) -> Optional[float]: + # get endpoint + endpoint = self.cfg.endpoints.find(chain_id=8453) + if not endpoint: + logger.error("Endpoint not found for mainnet to get superoethb_eth_ratio") + return None + ep = endpoint[0] + if not ep.connect(): + logger.error("Unable to connect endpoint for mainnet to get superoethb_eth_ratio") + return None + w3 = ep._web3 + if w3 is None: + logger.error("Unable to get web3 for mainnet to get superoethb_eth_ratio") + return None + # get usd price of superOETHb + try: + src_len = SRC_LEN + connectors = CONNECTORS + contract = w3.eth.contract(address=AERODROME_CONTRACT, abi=CONTRACT_ABI) + + contract_function = contract.functions.getManyRatesWithConnectors(src_len, connectors) + aerodrome_response = contract_function.call() + logger.info(f"Result: {aerodrome_response}") + response_int = aerodrome_response[0] + logger.info(f"Result: {response_int}") + response_usd_price = w3.fromWei(response_int, "ether") + logger.info(f"Result: {response_usd_price}") + response_price_float = float(response_usd_price) + logger.info(f"Result: {response_price_float}") + except Exception as e: + print(f"Error querying Aerodrome: {e}") + + return response_price_float + + async def get_price(self, asset: str, currency: str) -> OptionalDataPoint[float]: + """This implementation gets the superOETHb/ETH ratio by checking the oracle + price from Aerodrome's price oracle contract + """ + asset = asset.lower() + currency = currency.lower() + + source = eth_usd_median_feed.source + eth_price, timestamp = await source.fetch_new_datapoint() + if eth_price is None: + logger.error("Unable to get eth/usd price for superOETHb conversion") + return None, None + + usd_price = self.get_aerodrome_usd_price() + logger.info(f"superoethb usd_price: {usd_price}") + if usd_price is None: + logger.error("usd_price is None for superOETHb (check source)") + return None, None + + superoethb_eth_ratio = float(usd_price / eth_price) + if superoethb_eth_ratio is None: + logger.error("Unable to get superoethb_eth_ratio") + return None, None + + return superoethb_eth_ratio, datetime_now_utc() + + +@dataclass +class superoethbSpotPriceSource(PriceSource): + """Gets data from Aerodrome contract""" + + asset: str = "" + currency: str = "" + service: superOETHbSpotPriceService = field(default_factory=superOETHbSpotPriceService, init=False) + + +if __name__ == "__main__": + import asyncio + + async def main() -> None: + source = superoethbSpotPriceSource(asset="superoethb", currency="eth") + v, _ = await source.fetch_new_datapoint() + print(v) + + asyncio.run(main()) From 8cb6b56f5b7e78a1d6d5eec29b975192acb277ec Mon Sep 17 00:00:00 2001 From: 0xSpuddy Date: Thu, 3 Oct 2024 09:42:54 -0400 Subject: [PATCH 3/5] . --- scripts/aerodrome.py | 95 -------------------------------------------- 1 file changed, 95 deletions(-) delete mode 100644 scripts/aerodrome.py diff --git a/scripts/aerodrome.py b/scripts/aerodrome.py deleted file mode 100644 index fc4420fa..00000000 --- a/scripts/aerodrome.py +++ /dev/null @@ -1,95 +0,0 @@ -from dataclasses import dataclass -from dataclasses import field -from typing import Any -from typing import Optional - -from telliot_core.apps.telliot_config import TelliotConfig - -from telliot_feeds.dtypes.datapoint import OptionalDataPoint -from telliot_feeds.pricing.price_service import WebPriceService -from telliot_feeds.pricing.price_source import PriceSource -from telliot_feeds.sources.price.spot.coingecko import CoinGeckoSpotPriceSource -from telliot_feeds.sources.price.spot.coinpaprika import CoinpaprikaSpotPriceSource -from telliot_feeds.sources.price.spot.curvefiprice import CurveFiUSDPriceSource -from telliot_feeds.sources.price.spot.uniswapV3 import UniswapV3PriceSource -from telliot_feeds.sources.price_aggregator import PriceAggregator -from telliot_feeds.utils.log import get_logger - -logger = get_logger(__name__) - -# 2. Set the contract address and ABI -contract_address = "0xee717411f6E44F9feE011835C8E6FAaC5dEfF166" -contract_abi = [ - { - "inputs": [ - { - "internalType": "uint8", - "name": "src_len", - "type": "uint8" - }, - { - "internalType": "contract IERC20Metadata[]", - "name": "connectors", - "type": "address[]" - } - ], - "name": "getManyRatesWithConnectors", - "outputs": [ - { - "internalType": "uint256[]", - "name": "rates", - "type": "uint256[]" - } - ], - "stateMutability": "view", - "type": "function" - } -] - -# Function to query the contract -class aerodromeSpotPriceService(WebPriceService): - """Custom sFRAX Price Service""" - - def __init__(self, **kwargs: Any) -> None: - kwargs["name"] = "Custom sFRAX Price Service" - kwargs["url"] = "" - super().__init__(**kwargs) - self.cfg = TelliotConfig() - - def query_contract(src_len, connectors): - # get endpoint - endpoint = self.cfg.endpoints.find(chain_id=8453) - if not endpoint: - logger.error("Endpoint not found for mainnet to get sfrax_eth_ratio") - return None - ep = endpoint[0] - if not ep.connect(): - logger.error("Unable to connect endpoint for mainnet to get sfrax_eth_ratio") - return None - w3 = ep.web3 - contract = w3.eth.contract(address=contract_address, abi=contract_abi) - try: - # Get the contract function - contract_function = contract.functions.getManyRatesWithConnectors(src_len, connectors) - # Call the function and return the result - return contract_function.call() - except Exception as e: - print(f"Error querying contract: {e}") - return None - -@dataclass -class aerodromeSpotPriceSource(PriceSource): - asset: asset = "" - currency: currency = "" - service: aerodromeSpotPriceService = field(default_factory=aerodromeSpotPriceService, init=False) - - -if __name__ == "__main__": - import asyncio - - async def main() -> None: - source = aerodromeSpotPriceSource(src_len=1, connectors=[0xDBFeFD2e8460a6Ee4955A68582F85708BAEA60A3, 0x4200000000000000000000000000000000000006, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913]) - v, _ = await source.fetch_new_datapoint() - print(v) - - asyncio.run(main()) From b14db6554cdf9e940d8f55ec202340f53b8f65b6 Mon Sep 17 00:00:00 2001 From: 0xSpuddy Date: Thu, 3 Oct 2024 10:04:08 -0400 Subject: [PATCH 4/5] remove testing logs --- .../sources/superoethb_source.py | 32 ++++--------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/src/telliot_feeds/sources/superoethb_source.py b/src/telliot_feeds/sources/superoethb_source.py index 3b28149b..9e70b7f3 100644 --- a/src/telliot_feeds/sources/superoethb_source.py +++ b/src/telliot_feeds/sources/superoethb_source.py @@ -1,14 +1,14 @@ from dataclasses import dataclass from dataclasses import field from typing import Any -from typing import Optional from typing import List +from typing import Optional from telliot_core.apps.telliot_config import TelliotConfig from telliot_feeds.dtypes.datapoint import datetime_now_utc -from telliot_feeds.feeds.eth_usd_feed import eth_usd_median_feed from telliot_feeds.dtypes.datapoint import OptionalDataPoint +from telliot_feeds.feeds.eth_usd_feed import eth_usd_median_feed from telliot_feeds.pricing.price_service import WebPriceService from telliot_feeds.pricing.price_source import PriceSource from telliot_feeds.utils.log import get_logger @@ -19,34 +19,20 @@ CONTRACT_ABI = [ { "inputs": [ - { - "internalType": "uint8", - "name": "src_len", - "type": "uint8" - }, - { - "internalType": "contract IERC20Metadata[]", - "name": "connectors", - "type": "address[]" - } + {"internalType": "uint8", "name": "src_len", "type": "uint8"}, + {"internalType": "contract IERC20Metadata[]", "name": "connectors", "type": "address[]"}, ], "name": "getManyRatesWithConnectors", - "outputs": [ - { - "internalType": "uint256[]", - "name": "rates", - "type": "uint256[]" - } - ], + "outputs": [{"internalType": "uint256[]", "name": "rates", "type": "uint256[]"}], "stateMutability": "view", - "type": "function" + "type": "function", } ] SRC_LEN = 1 CONNECTORS = [ "0xDBFeFD2e8460a6Ee4955A68582F85708BAEA60A3", "0x4200000000000000000000000000000000000006", - "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" + "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", ] @@ -85,13 +71,9 @@ def get_aerodrome_usd_price(self) -> Optional[float]: contract_function = contract.functions.getManyRatesWithConnectors(src_len, connectors) aerodrome_response = contract_function.call() - logger.info(f"Result: {aerodrome_response}") response_int = aerodrome_response[0] - logger.info(f"Result: {response_int}") response_usd_price = w3.fromWei(response_int, "ether") - logger.info(f"Result: {response_usd_price}") response_price_float = float(response_usd_price) - logger.info(f"Result: {response_price_float}") except Exception as e: print(f"Error querying Aerodrome: {e}") From 550465468a86101ca9e540e51b873b68d75d1f5d Mon Sep 17 00:00:00 2001 From: 0xSpuddy Date: Thu, 3 Oct 2024 10:36:15 -0400 Subject: [PATCH 5/5] 2 sources in superOETHb test --- tests/feeds/test_superoethb_eth_feed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/feeds/test_superoethb_eth_feed.py b/tests/feeds/test_superoethb_eth_feed.py index a56662d4..3788198e 100644 --- a/tests/feeds/test_superoethb_eth_feed.py +++ b/tests/feeds/test_superoethb_eth_feed.py @@ -12,7 +12,7 @@ async def test_superoethb_eth_median_feed(caplog): assert v is not None assert v > 0 - assert "sources used in aggregate: 1" in caplog.text.lower() + assert "sources used in aggregate: 2" in caplog.text.lower() print(f"superOETHb/eth Price: {v}") # Get list of data sources from sources dict