intentkit 0.5.0__py3-none-any.whl → 0.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of intentkit might be problematic. Click here for more details.
- intentkit/__init__.py +17 -0
- intentkit/abstracts/__init__.py +0 -0
- intentkit/abstracts/agent.py +60 -0
- intentkit/abstracts/api.py +4 -0
- intentkit/abstracts/engine.py +38 -0
- intentkit/abstracts/exception.py +9 -0
- intentkit/abstracts/graph.py +25 -0
- intentkit/abstracts/skill.py +129 -0
- intentkit/abstracts/twitter.py +54 -0
- intentkit/clients/__init__.py +14 -0
- intentkit/clients/cdp.py +53 -0
- intentkit/clients/twitter.py +445 -0
- intentkit/config/__init__.py +0 -0
- intentkit/config/config.py +164 -0
- intentkit/core/__init__.py +0 -0
- intentkit/core/agent.py +191 -0
- intentkit/core/api.py +40 -0
- intentkit/core/client.py +45 -0
- intentkit/core/credit.py +1767 -0
- intentkit/core/engine.py +1018 -0
- intentkit/core/node.py +223 -0
- intentkit/core/prompt.py +58 -0
- intentkit/core/skill.py +124 -0
- intentkit/models/agent.py +1689 -0
- intentkit/models/agent_data.py +810 -0
- intentkit/models/agent_schema.json +733 -0
- intentkit/models/app_setting.py +156 -0
- intentkit/models/base.py +9 -0
- intentkit/models/chat.py +581 -0
- intentkit/models/conversation.py +286 -0
- intentkit/models/credit.py +1406 -0
- intentkit/models/db.py +120 -0
- intentkit/models/db_mig.py +102 -0
- intentkit/models/generator.py +347 -0
- intentkit/models/llm.py +746 -0
- intentkit/models/redis.py +132 -0
- intentkit/models/skill.py +466 -0
- intentkit/models/user.py +243 -0
- intentkit/skills/__init__.py +12 -0
- intentkit/skills/acolyt/__init__.py +83 -0
- intentkit/skills/acolyt/acolyt.jpg +0 -0
- intentkit/skills/acolyt/ask.py +128 -0
- intentkit/skills/acolyt/base.py +28 -0
- intentkit/skills/acolyt/schema.json +89 -0
- intentkit/skills/aixbt/README.md +71 -0
- intentkit/skills/aixbt/__init__.py +73 -0
- intentkit/skills/aixbt/aixbt.jpg +0 -0
- intentkit/skills/aixbt/base.py +21 -0
- intentkit/skills/aixbt/projects.py +153 -0
- intentkit/skills/aixbt/schema.json +99 -0
- intentkit/skills/allora/__init__.py +83 -0
- intentkit/skills/allora/allora.jpeg +0 -0
- intentkit/skills/allora/base.py +28 -0
- intentkit/skills/allora/price.py +130 -0
- intentkit/skills/allora/schema.json +89 -0
- intentkit/skills/base.py +174 -0
- intentkit/skills/carv/README.md +95 -0
- intentkit/skills/carv/__init__.py +121 -0
- intentkit/skills/carv/base.py +183 -0
- intentkit/skills/carv/carv.webp +0 -0
- intentkit/skills/carv/fetch_news.py +92 -0
- intentkit/skills/carv/onchain_query.py +164 -0
- intentkit/skills/carv/schema.json +137 -0
- intentkit/skills/carv/token_info_and_price.py +110 -0
- intentkit/skills/cdp/__init__.py +137 -0
- intentkit/skills/cdp/base.py +21 -0
- intentkit/skills/cdp/cdp.png +0 -0
- intentkit/skills/cdp/get_balance.py +81 -0
- intentkit/skills/cdp/schema.json +473 -0
- intentkit/skills/chainlist/README.md +38 -0
- intentkit/skills/chainlist/__init__.py +54 -0
- intentkit/skills/chainlist/base.py +21 -0
- intentkit/skills/chainlist/chain_lookup.py +208 -0
- intentkit/skills/chainlist/chainlist.png +0 -0
- intentkit/skills/chainlist/schema.json +47 -0
- intentkit/skills/common/__init__.py +82 -0
- intentkit/skills/common/base.py +21 -0
- intentkit/skills/common/common.jpg +0 -0
- intentkit/skills/common/current_time.py +84 -0
- intentkit/skills/common/schema.json +57 -0
- intentkit/skills/cookiefun/README.md +121 -0
- intentkit/skills/cookiefun/__init__.py +78 -0
- intentkit/skills/cookiefun/base.py +41 -0
- intentkit/skills/cookiefun/constants.py +18 -0
- intentkit/skills/cookiefun/cookiefun.png +0 -0
- intentkit/skills/cookiefun/get_account_details.py +171 -0
- intentkit/skills/cookiefun/get_account_feed.py +282 -0
- intentkit/skills/cookiefun/get_account_smart_followers.py +181 -0
- intentkit/skills/cookiefun/get_sectors.py +128 -0
- intentkit/skills/cookiefun/schema.json +155 -0
- intentkit/skills/cookiefun/search_accounts.py +225 -0
- intentkit/skills/cryptocompare/__init__.py +130 -0
- intentkit/skills/cryptocompare/api.py +159 -0
- intentkit/skills/cryptocompare/base.py +303 -0
- intentkit/skills/cryptocompare/cryptocompare.png +0 -0
- intentkit/skills/cryptocompare/fetch_news.py +96 -0
- intentkit/skills/cryptocompare/fetch_price.py +99 -0
- intentkit/skills/cryptocompare/fetch_top_exchanges.py +113 -0
- intentkit/skills/cryptocompare/fetch_top_market_cap.py +109 -0
- intentkit/skills/cryptocompare/fetch_top_volume.py +108 -0
- intentkit/skills/cryptocompare/fetch_trading_signals.py +107 -0
- intentkit/skills/cryptocompare/schema.json +168 -0
- intentkit/skills/cryptopanic/__init__.py +108 -0
- intentkit/skills/cryptopanic/base.py +51 -0
- intentkit/skills/cryptopanic/cryptopanic.png +0 -0
- intentkit/skills/cryptopanic/fetch_crypto_news.py +153 -0
- intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +136 -0
- intentkit/skills/cryptopanic/schema.json +103 -0
- intentkit/skills/dapplooker/README.md +92 -0
- intentkit/skills/dapplooker/__init__.py +83 -0
- intentkit/skills/dapplooker/base.py +26 -0
- intentkit/skills/dapplooker/dapplooker.jpg +0 -0
- intentkit/skills/dapplooker/dapplooker_token_data.py +476 -0
- intentkit/skills/dapplooker/schema.json +91 -0
- intentkit/skills/defillama/__init__.py +323 -0
- intentkit/skills/defillama/api.py +315 -0
- intentkit/skills/defillama/base.py +135 -0
- intentkit/skills/defillama/coins/__init__.py +0 -0
- intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +116 -0
- intentkit/skills/defillama/coins/fetch_block.py +98 -0
- intentkit/skills/defillama/coins/fetch_current_prices.py +105 -0
- intentkit/skills/defillama/coins/fetch_first_price.py +100 -0
- intentkit/skills/defillama/coins/fetch_historical_prices.py +110 -0
- intentkit/skills/defillama/coins/fetch_price_chart.py +109 -0
- intentkit/skills/defillama/coins/fetch_price_percentage.py +93 -0
- intentkit/skills/defillama/config/__init__.py +0 -0
- intentkit/skills/defillama/config/chains.py +433 -0
- intentkit/skills/defillama/defillama.jpeg +0 -0
- intentkit/skills/defillama/fees/__init__.py +0 -0
- intentkit/skills/defillama/fees/fetch_fees_overview.py +130 -0
- intentkit/skills/defillama/schema.json +383 -0
- intentkit/skills/defillama/stablecoins/__init__.py +0 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +100 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +129 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +83 -0
- intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +126 -0
- intentkit/skills/defillama/tests/__init__.py +0 -0
- intentkit/skills/defillama/tests/api_integration.test.py +192 -0
- intentkit/skills/defillama/tests/api_unit.test.py +583 -0
- intentkit/skills/defillama/tvl/__init__.py +0 -0
- intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +106 -0
- intentkit/skills/defillama/tvl/fetch_chains.py +107 -0
- intentkit/skills/defillama/tvl/fetch_historical_tvl.py +91 -0
- intentkit/skills/defillama/tvl/fetch_protocol.py +207 -0
- intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +93 -0
- intentkit/skills/defillama/tvl/fetch_protocols.py +196 -0
- intentkit/skills/defillama/volumes/__init__.py +0 -0
- intentkit/skills/defillama/volumes/fetch_dex_overview.py +157 -0
- intentkit/skills/defillama/volumes/fetch_dex_summary.py +123 -0
- intentkit/skills/defillama/volumes/fetch_options_overview.py +131 -0
- intentkit/skills/defillama/yields/__init__.py +0 -0
- intentkit/skills/defillama/yields/fetch_pool_chart.py +100 -0
- intentkit/skills/defillama/yields/fetch_pools.py +126 -0
- intentkit/skills/dexscreener/__init__.py +93 -0
- intentkit/skills/dexscreener/base.py +133 -0
- intentkit/skills/dexscreener/dexscreener.png +0 -0
- intentkit/skills/dexscreener/model/__init__.py +0 -0
- intentkit/skills/dexscreener/model/search_token_response.py +82 -0
- intentkit/skills/dexscreener/schema.json +48 -0
- intentkit/skills/dexscreener/search_token.py +321 -0
- intentkit/skills/dune_analytics/__init__.py +103 -0
- intentkit/skills/dune_analytics/base.py +46 -0
- intentkit/skills/dune_analytics/dune.png +0 -0
- intentkit/skills/dune_analytics/fetch_kol_buys.py +128 -0
- intentkit/skills/dune_analytics/fetch_nation_metrics.py +237 -0
- intentkit/skills/dune_analytics/schema.json +99 -0
- intentkit/skills/elfa/README.md +100 -0
- intentkit/skills/elfa/__init__.py +123 -0
- intentkit/skills/elfa/base.py +28 -0
- intentkit/skills/elfa/elfa.jpg +0 -0
- intentkit/skills/elfa/mention.py +504 -0
- intentkit/skills/elfa/schema.json +153 -0
- intentkit/skills/elfa/stats.py +118 -0
- intentkit/skills/elfa/tokens.py +126 -0
- intentkit/skills/enso/README.md +75 -0
- intentkit/skills/enso/__init__.py +114 -0
- intentkit/skills/enso/abi/__init__.py +0 -0
- intentkit/skills/enso/abi/approval.py +279 -0
- intentkit/skills/enso/abi/erc20.py +14 -0
- intentkit/skills/enso/abi/route.py +129 -0
- intentkit/skills/enso/base.py +44 -0
- intentkit/skills/enso/best_yield.py +286 -0
- intentkit/skills/enso/enso.jpg +0 -0
- intentkit/skills/enso/networks.py +105 -0
- intentkit/skills/enso/prices.py +93 -0
- intentkit/skills/enso/route.py +300 -0
- intentkit/skills/enso/schema.json +212 -0
- intentkit/skills/enso/tokens.py +223 -0
- intentkit/skills/enso/wallet.py +381 -0
- intentkit/skills/github/README.md +63 -0
- intentkit/skills/github/__init__.py +54 -0
- intentkit/skills/github/base.py +21 -0
- intentkit/skills/github/github.jpg +0 -0
- intentkit/skills/github/github_search.py +183 -0
- intentkit/skills/github/schema.json +59 -0
- intentkit/skills/heurist/__init__.py +143 -0
- intentkit/skills/heurist/base.py +26 -0
- intentkit/skills/heurist/heurist.png +0 -0
- intentkit/skills/heurist/image_generation_animagine_xl.py +162 -0
- intentkit/skills/heurist/image_generation_arthemy_comics.py +162 -0
- intentkit/skills/heurist/image_generation_arthemy_real.py +162 -0
- intentkit/skills/heurist/image_generation_braindance.py +162 -0
- intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +162 -0
- intentkit/skills/heurist/image_generation_flux_1_dev.py +162 -0
- intentkit/skills/heurist/image_generation_sdxl.py +161 -0
- intentkit/skills/heurist/schema.json +196 -0
- intentkit/skills/lifi/README.md +294 -0
- intentkit/skills/lifi/__init__.py +141 -0
- intentkit/skills/lifi/base.py +21 -0
- intentkit/skills/lifi/lifi.png +0 -0
- intentkit/skills/lifi/schema.json +89 -0
- intentkit/skills/lifi/token_execute.py +472 -0
- intentkit/skills/lifi/token_quote.py +190 -0
- intentkit/skills/lifi/utils.py +656 -0
- intentkit/skills/moralis/README.md +490 -0
- intentkit/skills/moralis/__init__.py +110 -0
- intentkit/skills/moralis/api.py +281 -0
- intentkit/skills/moralis/base.py +55 -0
- intentkit/skills/moralis/fetch_chain_portfolio.py +191 -0
- intentkit/skills/moralis/fetch_nft_portfolio.py +284 -0
- intentkit/skills/moralis/fetch_solana_portfolio.py +331 -0
- intentkit/skills/moralis/fetch_wallet_portfolio.py +301 -0
- intentkit/skills/moralis/moralis.png +0 -0
- intentkit/skills/moralis/schema.json +156 -0
- intentkit/skills/moralis/tests/__init__.py +0 -0
- intentkit/skills/moralis/tests/test_wallet.py +511 -0
- intentkit/skills/nation/__init__.py +62 -0
- intentkit/skills/nation/base.py +31 -0
- intentkit/skills/nation/nation.png +0 -0
- intentkit/skills/nation/nft_check.py +106 -0
- intentkit/skills/nation/schema.json +58 -0
- intentkit/skills/openai/__init__.py +107 -0
- intentkit/skills/openai/base.py +32 -0
- intentkit/skills/openai/dalle_image_generation.py +128 -0
- intentkit/skills/openai/gpt_image_generation.py +152 -0
- intentkit/skills/openai/gpt_image_to_image.py +186 -0
- intentkit/skills/openai/image_to_text.py +126 -0
- intentkit/skills/openai/openai.png +0 -0
- intentkit/skills/openai/schema.json +139 -0
- intentkit/skills/portfolio/README.md +55 -0
- intentkit/skills/portfolio/__init__.py +151 -0
- intentkit/skills/portfolio/base.py +107 -0
- intentkit/skills/portfolio/constants.py +9 -0
- intentkit/skills/portfolio/moralis.png +0 -0
- intentkit/skills/portfolio/schema.json +237 -0
- intentkit/skills/portfolio/token_balances.py +155 -0
- intentkit/skills/portfolio/wallet_approvals.py +102 -0
- intentkit/skills/portfolio/wallet_defi_positions.py +80 -0
- intentkit/skills/portfolio/wallet_history.py +155 -0
- intentkit/skills/portfolio/wallet_net_worth.py +112 -0
- intentkit/skills/portfolio/wallet_nfts.py +139 -0
- intentkit/skills/portfolio/wallet_profitability.py +101 -0
- intentkit/skills/portfolio/wallet_profitability_summary.py +91 -0
- intentkit/skills/portfolio/wallet_stats.py +79 -0
- intentkit/skills/portfolio/wallet_swaps.py +147 -0
- intentkit/skills/skills.toml +103 -0
- intentkit/skills/slack/__init__.py +98 -0
- intentkit/skills/slack/base.py +55 -0
- intentkit/skills/slack/get_channel.py +109 -0
- intentkit/skills/slack/get_message.py +136 -0
- intentkit/skills/slack/schedule_message.py +92 -0
- intentkit/skills/slack/schema.json +135 -0
- intentkit/skills/slack/send_message.py +81 -0
- intentkit/skills/slack/slack.jpg +0 -0
- intentkit/skills/system/__init__.py +90 -0
- intentkit/skills/system/base.py +22 -0
- intentkit/skills/system/read_agent_api_key.py +87 -0
- intentkit/skills/system/regenerate_agent_api_key.py +77 -0
- intentkit/skills/system/schema.json +53 -0
- intentkit/skills/system/system.svg +76 -0
- intentkit/skills/tavily/README.md +86 -0
- intentkit/skills/tavily/__init__.py +91 -0
- intentkit/skills/tavily/base.py +27 -0
- intentkit/skills/tavily/schema.json +119 -0
- intentkit/skills/tavily/tavily.jpg +0 -0
- intentkit/skills/tavily/tavily_extract.py +147 -0
- intentkit/skills/tavily/tavily_search.py +139 -0
- intentkit/skills/token/README.md +89 -0
- intentkit/skills/token/__init__.py +107 -0
- intentkit/skills/token/base.py +154 -0
- intentkit/skills/token/constants.py +9 -0
- intentkit/skills/token/erc20_transfers.py +145 -0
- intentkit/skills/token/moralis.png +0 -0
- intentkit/skills/token/schema.json +141 -0
- intentkit/skills/token/token_analytics.py +81 -0
- intentkit/skills/token/token_price.py +132 -0
- intentkit/skills/token/token_search.py +121 -0
- intentkit/skills/twitter/__init__.py +146 -0
- intentkit/skills/twitter/base.py +68 -0
- intentkit/skills/twitter/follow_user.py +69 -0
- intentkit/skills/twitter/get_mentions.py +124 -0
- intentkit/skills/twitter/get_timeline.py +111 -0
- intentkit/skills/twitter/get_user_by_username.py +84 -0
- intentkit/skills/twitter/get_user_tweets.py +123 -0
- intentkit/skills/twitter/like_tweet.py +65 -0
- intentkit/skills/twitter/post_tweet.py +90 -0
- intentkit/skills/twitter/reply_tweet.py +98 -0
- intentkit/skills/twitter/retweet.py +76 -0
- intentkit/skills/twitter/schema.json +258 -0
- intentkit/skills/twitter/search_tweets.py +115 -0
- intentkit/skills/twitter/twitter.png +0 -0
- intentkit/skills/unrealspeech/__init__.py +55 -0
- intentkit/skills/unrealspeech/base.py +21 -0
- intentkit/skills/unrealspeech/schema.json +100 -0
- intentkit/skills/unrealspeech/text_to_speech.py +177 -0
- intentkit/skills/unrealspeech/unrealspeech.jpg +0 -0
- intentkit/skills/venice_audio/__init__.py +106 -0
- intentkit/skills/venice_audio/base.py +119 -0
- intentkit/skills/venice_audio/input.py +41 -0
- intentkit/skills/venice_audio/schema.json +152 -0
- intentkit/skills/venice_audio/venice_audio.py +240 -0
- intentkit/skills/venice_audio/venice_logo.jpg +0 -0
- intentkit/skills/venice_image/README.md +119 -0
- intentkit/skills/venice_image/__init__.py +154 -0
- intentkit/skills/venice_image/api.py +138 -0
- intentkit/skills/venice_image/base.py +188 -0
- intentkit/skills/venice_image/config.py +35 -0
- intentkit/skills/venice_image/image_enhance/README.md +119 -0
- intentkit/skills/venice_image/image_enhance/__init__.py +0 -0
- intentkit/skills/venice_image/image_enhance/image_enhance.py +80 -0
- intentkit/skills/venice_image/image_enhance/image_enhance_base.py +23 -0
- intentkit/skills/venice_image/image_enhance/image_enhance_input.py +40 -0
- intentkit/skills/venice_image/image_generation/README.md +144 -0
- intentkit/skills/venice_image/image_generation/__init__.py +0 -0
- intentkit/skills/venice_image/image_generation/image_generation_base.py +117 -0
- intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -0
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -0
- intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -0
- intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -0
- intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -0
- intentkit/skills/venice_image/image_upscale/README.md +111 -0
- intentkit/skills/venice_image/image_upscale/__init__.py +0 -0
- intentkit/skills/venice_image/image_upscale/image_upscale.py +90 -0
- intentkit/skills/venice_image/image_upscale/image_upscale_base.py +23 -0
- intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -0
- intentkit/skills/venice_image/image_vision/README.md +112 -0
- intentkit/skills/venice_image/image_vision/__init__.py +0 -0
- intentkit/skills/venice_image/image_vision/image_vision.py +100 -0
- intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -0
- intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -0
- intentkit/skills/venice_image/schema.json +267 -0
- intentkit/skills/venice_image/utils.py +78 -0
- intentkit/skills/venice_image/venice_image.jpg +0 -0
- intentkit/skills/web_scraper/README.md +82 -0
- intentkit/skills/web_scraper/__init__.py +92 -0
- intentkit/skills/web_scraper/base.py +21 -0
- intentkit/skills/web_scraper/langchain.png +0 -0
- intentkit/skills/web_scraper/schema.json +115 -0
- intentkit/skills/web_scraper/scrape_and_index.py +327 -0
- intentkit/utils/__init__.py +1 -0
- intentkit/utils/chain.py +436 -0
- intentkit/utils/error.py +134 -0
- intentkit/utils/logging.py +70 -0
- intentkit/utils/middleware.py +61 -0
- intentkit/utils/random.py +16 -0
- intentkit/utils/s3.py +267 -0
- intentkit/utils/slack_alert.py +79 -0
- intentkit/utils/tx.py +37 -0
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/METADATA +1 -1
- intentkit-0.5.2.dist-info/RECORD +365 -0
- intentkit-0.5.0.dist-info/RECORD +0 -4
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/WHEEL +0 -0
- {intentkit-0.5.0.dist-info → intentkit-0.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import AsyncMock, patch
|
|
3
|
+
|
|
4
|
+
# Import the endpoints from your module.
|
|
5
|
+
# Adjust the import path if your module has a different name or location.
|
|
6
|
+
from intentkit.skills.defillama.api import (
|
|
7
|
+
fetch_batch_historical_prices,
|
|
8
|
+
fetch_block,
|
|
9
|
+
fetch_chain_historical_tvl,
|
|
10
|
+
fetch_chains,
|
|
11
|
+
fetch_current_prices,
|
|
12
|
+
# Volume related functions
|
|
13
|
+
fetch_dex_overview,
|
|
14
|
+
fetch_dex_summary,
|
|
15
|
+
# Fees related functions
|
|
16
|
+
fetch_fees_overview,
|
|
17
|
+
fetch_first_price,
|
|
18
|
+
fetch_historical_prices,
|
|
19
|
+
fetch_historical_tvl,
|
|
20
|
+
fetch_options_overview,
|
|
21
|
+
fetch_pool_chart,
|
|
22
|
+
# Yields related functions
|
|
23
|
+
fetch_pools,
|
|
24
|
+
# Price related functions
|
|
25
|
+
fetch_price_chart,
|
|
26
|
+
fetch_price_percentage,
|
|
27
|
+
fetch_protocol,
|
|
28
|
+
fetch_protocol_current_tvl,
|
|
29
|
+
# Original functions
|
|
30
|
+
fetch_protocols,
|
|
31
|
+
fetch_stablecoin_chains,
|
|
32
|
+
fetch_stablecoin_charts,
|
|
33
|
+
fetch_stablecoin_prices,
|
|
34
|
+
# Stablecoin related functions
|
|
35
|
+
fetch_stablecoins,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Dummy response to simulate httpx responses.
|
|
40
|
+
class DummyResponse:
|
|
41
|
+
def __init__(self, status_code, json_data):
|
|
42
|
+
self.status_code = status_code
|
|
43
|
+
self._json_data = json_data
|
|
44
|
+
|
|
45
|
+
def json(self):
|
|
46
|
+
return self._json_data
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestDefiLlamaAPI(unittest.IsolatedAsyncioTestCase):
|
|
50
|
+
@classmethod
|
|
51
|
+
def setUpClass(cls):
|
|
52
|
+
# Set up a fixed timestamp that all tests will use
|
|
53
|
+
cls.mock_timestamp = 1677648000 # Fixed timestamp
|
|
54
|
+
|
|
55
|
+
async def asyncSetUp(self):
|
|
56
|
+
# Start the patcher before each test
|
|
57
|
+
self.datetime_patcher = patch("skills.defillama.api.datetime")
|
|
58
|
+
self.mock_datetime = self.datetime_patcher.start()
|
|
59
|
+
# Configure the mock to return our fixed timestamp
|
|
60
|
+
self.mock_datetime.now.return_value.timestamp.return_value = self.mock_timestamp
|
|
61
|
+
|
|
62
|
+
async def asyncTearDown(self):
|
|
63
|
+
# Stop the patcher after each test
|
|
64
|
+
self.datetime_patcher.stop()
|
|
65
|
+
|
|
66
|
+
# Helper method to patch httpx.AsyncClient and set up the dummy client.
|
|
67
|
+
async def _run_with_dummy(
|
|
68
|
+
self, func, expected_url, dummy_response, *args, expected_kwargs=None
|
|
69
|
+
):
|
|
70
|
+
if expected_kwargs is None:
|
|
71
|
+
expected_kwargs = {}
|
|
72
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
73
|
+
client_instance = AsyncMock()
|
|
74
|
+
client_instance.get.return_value = dummy_response
|
|
75
|
+
# Ensure that __aenter__ returns our dummy client.
|
|
76
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
77
|
+
result = await func(*args)
|
|
78
|
+
# Check that the get call was made with the expected URL (and parameters, if any).
|
|
79
|
+
client_instance.get.assert_called_once_with(expected_url, **expected_kwargs)
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
# --- Tests for fetch_protocols ---
|
|
83
|
+
async def test_fetch_protocols_success(self):
|
|
84
|
+
dummy = DummyResponse(200, {"protocols": []})
|
|
85
|
+
result = await self._run_with_dummy(
|
|
86
|
+
fetch_protocols,
|
|
87
|
+
"https://api.llama.fi/protocols",
|
|
88
|
+
dummy,
|
|
89
|
+
)
|
|
90
|
+
self.assertEqual(result, {"protocols": []})
|
|
91
|
+
|
|
92
|
+
async def test_fetch_protocols_error(self):
|
|
93
|
+
dummy = DummyResponse(404, None)
|
|
94
|
+
result = await self._run_with_dummy(
|
|
95
|
+
fetch_protocols,
|
|
96
|
+
"https://api.llama.fi/protocols",
|
|
97
|
+
dummy,
|
|
98
|
+
)
|
|
99
|
+
self.assertEqual(result, {"error": "API returned status code 404"})
|
|
100
|
+
|
|
101
|
+
# --- Tests for fetch_protocol ---
|
|
102
|
+
async def test_fetch_protocol_success(self):
|
|
103
|
+
protocol = "testprotocol"
|
|
104
|
+
dummy = DummyResponse(200, {"protocol": protocol})
|
|
105
|
+
expected_url = f"https://api.llama.fi/protocol/{protocol}"
|
|
106
|
+
result = await self._run_with_dummy(
|
|
107
|
+
fetch_protocol, expected_url, dummy, protocol
|
|
108
|
+
)
|
|
109
|
+
self.assertEqual(result, {"protocol": protocol})
|
|
110
|
+
|
|
111
|
+
async def test_fetch_protocol_error(self):
|
|
112
|
+
protocol = "testprotocol"
|
|
113
|
+
dummy = DummyResponse(500, None)
|
|
114
|
+
expected_url = f"https://api.llama.fi/protocol/{protocol}"
|
|
115
|
+
result = await self._run_with_dummy(
|
|
116
|
+
fetch_protocol, expected_url, dummy, protocol
|
|
117
|
+
)
|
|
118
|
+
self.assertEqual(result, {"error": "API returned status code 500"})
|
|
119
|
+
|
|
120
|
+
# --- Tests for fetch_historical_tvl ---
|
|
121
|
+
async def test_fetch_historical_tvl_success(self):
|
|
122
|
+
dummy = DummyResponse(200, {"historical": "data"})
|
|
123
|
+
expected_url = "https://api.llama.fi/v2/historicalChainTvl"
|
|
124
|
+
result = await self._run_with_dummy(
|
|
125
|
+
fetch_historical_tvl,
|
|
126
|
+
expected_url,
|
|
127
|
+
dummy,
|
|
128
|
+
)
|
|
129
|
+
self.assertEqual(result, {"historical": "data"})
|
|
130
|
+
|
|
131
|
+
async def test_fetch_historical_tvl_error(self):
|
|
132
|
+
dummy = DummyResponse(400, None)
|
|
133
|
+
expected_url = "https://api.llama.fi/v2/historicalChainTvl"
|
|
134
|
+
result = await self._run_with_dummy(
|
|
135
|
+
fetch_historical_tvl,
|
|
136
|
+
expected_url,
|
|
137
|
+
dummy,
|
|
138
|
+
)
|
|
139
|
+
self.assertEqual(result, {"error": "API returned status code 400"})
|
|
140
|
+
|
|
141
|
+
# --- Tests for fetch_chain_historical_tvl ---
|
|
142
|
+
async def test_fetch_chain_historical_tvl_success(self):
|
|
143
|
+
chain = "ethereum"
|
|
144
|
+
dummy = DummyResponse(200, {"chain": chain})
|
|
145
|
+
expected_url = f"https://api.llama.fi/v2/historicalChainTvl/{chain}"
|
|
146
|
+
result = await self._run_with_dummy(
|
|
147
|
+
fetch_chain_historical_tvl, expected_url, dummy, chain
|
|
148
|
+
)
|
|
149
|
+
self.assertEqual(result, {"chain": chain})
|
|
150
|
+
|
|
151
|
+
async def test_fetch_chain_historical_tvl_error(self):
|
|
152
|
+
chain = "ethereum"
|
|
153
|
+
dummy = DummyResponse(503, None)
|
|
154
|
+
expected_url = f"https://api.llama.fi/v2/historicalChainTvl/{chain}"
|
|
155
|
+
result = await self._run_with_dummy(
|
|
156
|
+
fetch_chain_historical_tvl, expected_url, dummy, chain
|
|
157
|
+
)
|
|
158
|
+
self.assertEqual(result, {"error": "API returned status code 503"})
|
|
159
|
+
|
|
160
|
+
# --- Tests for fetch_protocol_current_tvl ---
|
|
161
|
+
async def test_fetch_protocol_current_tvl_success(self):
|
|
162
|
+
protocol = "testprotocol"
|
|
163
|
+
dummy = DummyResponse(200, {"current_tvl": 12345})
|
|
164
|
+
expected_url = f"https://api.llama.fi/tvl/{protocol}"
|
|
165
|
+
result = await self._run_with_dummy(
|
|
166
|
+
fetch_protocol_current_tvl, expected_url, dummy, protocol
|
|
167
|
+
)
|
|
168
|
+
self.assertEqual(result, {"current_tvl": 12345})
|
|
169
|
+
|
|
170
|
+
async def test_fetch_protocol_current_tvl_error(self):
|
|
171
|
+
protocol = "testprotocol"
|
|
172
|
+
dummy = DummyResponse(418, None)
|
|
173
|
+
expected_url = f"https://api.llama.fi/tvl/{protocol}"
|
|
174
|
+
result = await self._run_with_dummy(
|
|
175
|
+
fetch_protocol_current_tvl, expected_url, dummy, protocol
|
|
176
|
+
)
|
|
177
|
+
self.assertEqual(result, {"error": "API returned status code 418"})
|
|
178
|
+
|
|
179
|
+
# --- Tests for fetch_chains ---
|
|
180
|
+
async def test_fetch_chains_success(self):
|
|
181
|
+
dummy = DummyResponse(200, {"chains": ["eth", "bsc"]})
|
|
182
|
+
expected_url = "https://api.llama.fi/v2/chains"
|
|
183
|
+
result = await self._run_with_dummy(
|
|
184
|
+
fetch_chains,
|
|
185
|
+
expected_url,
|
|
186
|
+
dummy,
|
|
187
|
+
)
|
|
188
|
+
self.assertEqual(result, {"chains": ["eth", "bsc"]})
|
|
189
|
+
|
|
190
|
+
async def test_fetch_chains_error(self):
|
|
191
|
+
dummy = DummyResponse(404, None)
|
|
192
|
+
expected_url = "https://api.llama.fi/v2/chains"
|
|
193
|
+
result = await self._run_with_dummy(
|
|
194
|
+
fetch_chains,
|
|
195
|
+
expected_url,
|
|
196
|
+
dummy,
|
|
197
|
+
)
|
|
198
|
+
self.assertEqual(result, {"error": "API returned status code 404"})
|
|
199
|
+
|
|
200
|
+
# --- Tests for fetch_current_prices ---
|
|
201
|
+
async def test_fetch_current_prices_success(self):
|
|
202
|
+
coins = ["coin1", "coin2"]
|
|
203
|
+
coins_str = ",".join(coins)
|
|
204
|
+
dummy = DummyResponse(200, {"prices": "data"})
|
|
205
|
+
expected_url = f"https://api.llama.fi/prices/current/{coins_str}?searchWidth=4h"
|
|
206
|
+
result = await self._run_with_dummy(
|
|
207
|
+
fetch_current_prices, expected_url, dummy, coins
|
|
208
|
+
)
|
|
209
|
+
self.assertEqual(result, {"prices": "data"})
|
|
210
|
+
|
|
211
|
+
async def test_fetch_current_prices_error(self):
|
|
212
|
+
coins = ["coin1", "coin2"]
|
|
213
|
+
coins_str = ",".join(coins)
|
|
214
|
+
dummy = DummyResponse(500, None)
|
|
215
|
+
expected_url = f"https://api.llama.fi/prices/current/{coins_str}?searchWidth=4h"
|
|
216
|
+
result = await self._run_with_dummy(
|
|
217
|
+
fetch_current_prices, expected_url, dummy, coins
|
|
218
|
+
)
|
|
219
|
+
self.assertEqual(result, {"error": "API returned status code 500"})
|
|
220
|
+
|
|
221
|
+
# --- Tests for fetch_historical_prices ---
|
|
222
|
+
async def test_fetch_historical_prices_success(self):
|
|
223
|
+
timestamp = 1609459200
|
|
224
|
+
coins = ["coin1", "coin2"]
|
|
225
|
+
coins_str = ",".join(coins)
|
|
226
|
+
dummy = DummyResponse(200, {"historical_prices": "data"})
|
|
227
|
+
expected_url = f"https://api.llama.fi/prices/historical/{timestamp}/{coins_str}?searchWidth=4h"
|
|
228
|
+
result = await self._run_with_dummy(
|
|
229
|
+
fetch_historical_prices, expected_url, dummy, timestamp, coins
|
|
230
|
+
)
|
|
231
|
+
self.assertEqual(result, {"historical_prices": "data"})
|
|
232
|
+
|
|
233
|
+
async def test_fetch_historical_prices_error(self):
|
|
234
|
+
timestamp = 1609459200
|
|
235
|
+
coins = ["coin1", "coin2"]
|
|
236
|
+
coins_str = ",".join(coins)
|
|
237
|
+
dummy = DummyResponse(400, None)
|
|
238
|
+
expected_url = f"https://api.llama.fi/prices/historical/{timestamp}/{coins_str}?searchWidth=4h"
|
|
239
|
+
result = await self._run_with_dummy(
|
|
240
|
+
fetch_historical_prices, expected_url, dummy, timestamp, coins
|
|
241
|
+
)
|
|
242
|
+
self.assertEqual(result, {"error": "API returned status code 400"})
|
|
243
|
+
|
|
244
|
+
# --- Tests for fetch_batch_historical_prices ---
|
|
245
|
+
async def test_fetch_batch_historical_prices_success(self):
|
|
246
|
+
coins_timestamps = {"coin1": [1609459200, 1609545600], "coin2": [1609459200]}
|
|
247
|
+
dummy = DummyResponse(200, {"batch": "data"})
|
|
248
|
+
expected_url = "https://api.llama.fi/batchHistorical"
|
|
249
|
+
# For this endpoint, a params dict is sent.
|
|
250
|
+
expected_params = {"coins": coins_timestamps, "searchWidth": "600"}
|
|
251
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
252
|
+
client_instance = AsyncMock()
|
|
253
|
+
client_instance.get.return_value = dummy
|
|
254
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
255
|
+
result = await fetch_batch_historical_prices(coins_timestamps)
|
|
256
|
+
client_instance.get.assert_called_once_with(
|
|
257
|
+
expected_url, params=expected_params
|
|
258
|
+
)
|
|
259
|
+
self.assertEqual(result, {"batch": "data"})
|
|
260
|
+
|
|
261
|
+
async def test_fetch_batch_historical_prices_error(self):
|
|
262
|
+
coins_timestamps = {"coin1": [1609459200], "coin2": [1609459200]}
|
|
263
|
+
dummy = DummyResponse(503, None)
|
|
264
|
+
expected_url = "https://api.llama.fi/batchHistorical"
|
|
265
|
+
expected_params = {"coins": coins_timestamps, "searchWidth": "600"}
|
|
266
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
267
|
+
client_instance = AsyncMock()
|
|
268
|
+
client_instance.get.return_value = dummy
|
|
269
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
270
|
+
result = await fetch_batch_historical_prices(coins_timestamps)
|
|
271
|
+
client_instance.get.assert_called_once_with(
|
|
272
|
+
expected_url, params=expected_params
|
|
273
|
+
)
|
|
274
|
+
self.assertEqual(result, {"error": "API returned status code 503"})
|
|
275
|
+
|
|
276
|
+
async def test_fetch_price_chart_success(self):
|
|
277
|
+
coins = ["bitcoin", "ethereum"]
|
|
278
|
+
coins_str = ",".join(coins)
|
|
279
|
+
dummy = DummyResponse(200, {"chart": "data"})
|
|
280
|
+
expected_url = f"https://api.llama.fi/chart/{coins_str}"
|
|
281
|
+
|
|
282
|
+
# Calculate start time based on mock timestamp
|
|
283
|
+
start_time = self.mock_timestamp - 86400 # mock timestamp - 1 day
|
|
284
|
+
expected_params = {
|
|
285
|
+
"start": start_time,
|
|
286
|
+
"span": 10,
|
|
287
|
+
"period": "2d",
|
|
288
|
+
"searchWidth": "600",
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
292
|
+
client_instance = AsyncMock()
|
|
293
|
+
client_instance.get.return_value = dummy
|
|
294
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
295
|
+
result = await fetch_price_chart(coins)
|
|
296
|
+
client_instance.get.assert_called_once_with(
|
|
297
|
+
expected_url, params=expected_params
|
|
298
|
+
)
|
|
299
|
+
self.assertEqual(result, {"chart": "data"})
|
|
300
|
+
|
|
301
|
+
async def test_fetch_price_chart_error(self):
|
|
302
|
+
coins = ["bitcoin", "ethereum"]
|
|
303
|
+
coins_str = ",".join(coins)
|
|
304
|
+
dummy = DummyResponse(500, None)
|
|
305
|
+
expected_url = f"https://api.llama.fi/chart/{coins_str}"
|
|
306
|
+
|
|
307
|
+
# Calculate start time based on mock timestamp
|
|
308
|
+
start_time = self.mock_timestamp - 86400 # mock timestamp - 1 day
|
|
309
|
+
expected_params = {
|
|
310
|
+
"start": start_time,
|
|
311
|
+
"span": 10,
|
|
312
|
+
"period": "2d",
|
|
313
|
+
"searchWidth": "600",
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
317
|
+
client_instance = AsyncMock()
|
|
318
|
+
client_instance.get.return_value = dummy
|
|
319
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
320
|
+
result = await fetch_price_chart(coins)
|
|
321
|
+
client_instance.get.assert_called_once_with(
|
|
322
|
+
expected_url, params=expected_params
|
|
323
|
+
)
|
|
324
|
+
self.assertEqual(result, {"error": "API returned status code 500"})
|
|
325
|
+
|
|
326
|
+
# --- Tests for fetch_price_percentage ---
|
|
327
|
+
async def test_fetch_price_percentage_success(self):
|
|
328
|
+
coins = ["bitcoin", "ethereum"]
|
|
329
|
+
coins_str = ",".join(coins)
|
|
330
|
+
dummy = DummyResponse(200, {"percentage": "data"})
|
|
331
|
+
expected_url = f"https://api.llama.fi/percentage/{coins_str}"
|
|
332
|
+
|
|
333
|
+
mock_timestamp = 1677648000 # Fixed timestamp
|
|
334
|
+
with patch("skills.defillama.api.datetime") as mock_datetime:
|
|
335
|
+
mock_datetime.now.return_value.timestamp.return_value = mock_timestamp
|
|
336
|
+
expected_params = {
|
|
337
|
+
"timestamp": mock_timestamp,
|
|
338
|
+
"lookForward": "false",
|
|
339
|
+
"period": "24h",
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
343
|
+
client_instance = AsyncMock()
|
|
344
|
+
client_instance.get.return_value = dummy
|
|
345
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
346
|
+
result = await fetch_price_percentage(coins)
|
|
347
|
+
client_instance.get.assert_called_once_with(
|
|
348
|
+
expected_url, params=expected_params
|
|
349
|
+
)
|
|
350
|
+
self.assertEqual(result, {"percentage": "data"})
|
|
351
|
+
|
|
352
|
+
async def test_fetch_price_percentage_error(self):
|
|
353
|
+
coins = ["bitcoin", "ethereum"]
|
|
354
|
+
coins_str = ",".join(coins)
|
|
355
|
+
dummy = DummyResponse(404, None)
|
|
356
|
+
expected_url = f"https://api.llama.fi/percentage/{coins_str}"
|
|
357
|
+
|
|
358
|
+
expected_params = {
|
|
359
|
+
"timestamp": self.mock_timestamp,
|
|
360
|
+
"lookForward": "false",
|
|
361
|
+
"period": "24h",
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
365
|
+
client_instance = AsyncMock()
|
|
366
|
+
client_instance.get.return_value = dummy
|
|
367
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
368
|
+
result = await fetch_price_percentage(coins)
|
|
369
|
+
client_instance.get.assert_called_once_with(
|
|
370
|
+
expected_url, params=expected_params
|
|
371
|
+
)
|
|
372
|
+
self.assertEqual(result, {"error": "API returned status code 404"})
|
|
373
|
+
|
|
374
|
+
async def test_fetch_price_percentage_error2(self):
|
|
375
|
+
coins = ["bitcoin", "ethereum"]
|
|
376
|
+
coins_str = ",".join(coins)
|
|
377
|
+
dummy = DummyResponse(404, None)
|
|
378
|
+
expected_url = f"https://api.llama.fi/percentage/{coins_str}"
|
|
379
|
+
|
|
380
|
+
with patch("datetime.datetime") as mock_datetime:
|
|
381
|
+
mock_datetime.now.return_value.timestamp.return_value = 1677648000
|
|
382
|
+
expected_params = {
|
|
383
|
+
"timestamp": 1677648000,
|
|
384
|
+
"lookForward": "false",
|
|
385
|
+
"period": "24h",
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
389
|
+
client_instance = AsyncMock()
|
|
390
|
+
client_instance.get.return_value = dummy
|
|
391
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
392
|
+
result = await fetch_price_percentage(coins)
|
|
393
|
+
client_instance.get.assert_called_once_with(
|
|
394
|
+
expected_url, params=expected_params
|
|
395
|
+
)
|
|
396
|
+
self.assertEqual(result, {"error": "API returned status code 404"})
|
|
397
|
+
|
|
398
|
+
# --- Tests for fetch_first_price ---
|
|
399
|
+
async def test_fetch_first_price_success(self):
|
|
400
|
+
coins = ["bitcoin", "ethereum"]
|
|
401
|
+
coins_str = ",".join(coins)
|
|
402
|
+
dummy = DummyResponse(200, {"first_prices": "data"})
|
|
403
|
+
expected_url = f"https://api.llama.fi/prices/first/{coins_str}"
|
|
404
|
+
result = await self._run_with_dummy(
|
|
405
|
+
fetch_first_price, expected_url, dummy, coins
|
|
406
|
+
)
|
|
407
|
+
self.assertEqual(result, {"first_prices": "data"})
|
|
408
|
+
|
|
409
|
+
async def test_fetch_first_price_error(self):
|
|
410
|
+
coins = ["bitcoin", "ethereum"]
|
|
411
|
+
coins_str = ",".join(coins)
|
|
412
|
+
dummy = DummyResponse(500, None)
|
|
413
|
+
expected_url = f"https://api.llama.fi/prices/first/{coins_str}"
|
|
414
|
+
result = await self._run_with_dummy(
|
|
415
|
+
fetch_first_price, expected_url, dummy, coins
|
|
416
|
+
)
|
|
417
|
+
self.assertEqual(result, {"error": "API returned status code 500"})
|
|
418
|
+
|
|
419
|
+
# --- Tests for fetch_block ---
|
|
420
|
+
async def test_fetch_block_success(self):
|
|
421
|
+
chain = "ethereum"
|
|
422
|
+
dummy = DummyResponse(200, {"block": 123456})
|
|
423
|
+
mock_timestamp = 1677648000 # Fixed timestamp
|
|
424
|
+
|
|
425
|
+
with patch("skills.defillama.api.datetime") as mock_datetime:
|
|
426
|
+
mock_datetime.now.return_value.timestamp.return_value = mock_timestamp
|
|
427
|
+
expected_url = f"https://api.llama.fi/block/{chain}/{mock_timestamp}"
|
|
428
|
+
result = await self._run_with_dummy(fetch_block, expected_url, dummy, chain)
|
|
429
|
+
self.assertEqual(result, {"block": 123456})
|
|
430
|
+
|
|
431
|
+
async def test_fetch_block_error(self):
|
|
432
|
+
chain = "ethereum"
|
|
433
|
+
dummy = DummyResponse(404, None)
|
|
434
|
+
mock_timestamp = 1677648000 # Fixed timestamp
|
|
435
|
+
|
|
436
|
+
with patch("skills.defillama.api.datetime") as mock_datetime:
|
|
437
|
+
mock_datetime.now.return_value.timestamp.return_value = mock_timestamp
|
|
438
|
+
expected_url = f"https://api.llama.fi/block/{chain}/{mock_timestamp}"
|
|
439
|
+
result = await self._run_with_dummy(fetch_block, expected_url, dummy, chain)
|
|
440
|
+
self.assertEqual(result, {"error": "API returned status code 404"})
|
|
441
|
+
|
|
442
|
+
# --- Tests for Stablecoins API ---
|
|
443
|
+
async def test_fetch_stablecoins_success(self):
|
|
444
|
+
dummy = DummyResponse(200, {"stablecoins": "data"})
|
|
445
|
+
expected_url = "https://api.llama.fi/stablecoins"
|
|
446
|
+
expected_params = {"includePrices": "true"}
|
|
447
|
+
|
|
448
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
449
|
+
client_instance = AsyncMock()
|
|
450
|
+
client_instance.get.return_value = dummy
|
|
451
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
452
|
+
result = await fetch_stablecoins()
|
|
453
|
+
client_instance.get.assert_called_once_with(
|
|
454
|
+
expected_url, params=expected_params
|
|
455
|
+
)
|
|
456
|
+
self.assertEqual(result, {"stablecoins": "data"})
|
|
457
|
+
|
|
458
|
+
async def test_fetch_stablecoin_charts_success(self):
|
|
459
|
+
stablecoin_id = "USDT"
|
|
460
|
+
chain = "ethereum"
|
|
461
|
+
dummy = DummyResponse(200, {"charts": "data"})
|
|
462
|
+
expected_url = (
|
|
463
|
+
f"https://api.llama.fi/stablecoincharts/{chain}?stablecoin={stablecoin_id}"
|
|
464
|
+
)
|
|
465
|
+
result = await self._run_with_dummy(
|
|
466
|
+
fetch_stablecoin_charts, expected_url, dummy, stablecoin_id, chain
|
|
467
|
+
)
|
|
468
|
+
self.assertEqual(result, {"charts": "data"})
|
|
469
|
+
|
|
470
|
+
async def test_fetch_stablecoin_chains_success(self):
|
|
471
|
+
dummy = DummyResponse(200, {"chains": "data"})
|
|
472
|
+
expected_url = "https://api.llama.fi/stablecoinchains"
|
|
473
|
+
result = await self._run_with_dummy(
|
|
474
|
+
fetch_stablecoin_chains, expected_url, dummy
|
|
475
|
+
)
|
|
476
|
+
self.assertEqual(result, {"chains": "data"})
|
|
477
|
+
|
|
478
|
+
async def test_fetch_stablecoin_prices_success(self):
|
|
479
|
+
dummy = DummyResponse(200, {"prices": "data"})
|
|
480
|
+
expected_url = "https://api.llama.fi/stablecoinprices"
|
|
481
|
+
result = await self._run_with_dummy(
|
|
482
|
+
fetch_stablecoin_prices, expected_url, dummy
|
|
483
|
+
)
|
|
484
|
+
self.assertEqual(result, {"prices": "data"})
|
|
485
|
+
|
|
486
|
+
# --- Tests for Yields API ---
|
|
487
|
+
async def test_fetch_pools_success(self):
|
|
488
|
+
dummy = DummyResponse(200, {"pools": "data"})
|
|
489
|
+
expected_url = "https://api.llama.fi/pools"
|
|
490
|
+
result = await self._run_with_dummy(fetch_pools, expected_url, dummy)
|
|
491
|
+
self.assertEqual(result, {"pools": "data"})
|
|
492
|
+
|
|
493
|
+
async def test_fetch_pool_chart_success(self):
|
|
494
|
+
pool_id = "compound-usdc"
|
|
495
|
+
dummy = DummyResponse(200, {"chart": "data"})
|
|
496
|
+
expected_url = f"https://api.llama.fi/chart/{pool_id}"
|
|
497
|
+
result = await self._run_with_dummy(
|
|
498
|
+
fetch_pool_chart, expected_url, dummy, pool_id
|
|
499
|
+
)
|
|
500
|
+
self.assertEqual(result, {"chart": "data"})
|
|
501
|
+
|
|
502
|
+
# --- Tests for Volumes API ---
|
|
503
|
+
async def test_fetch_dex_overview_success(self):
|
|
504
|
+
dummy = DummyResponse(200, {"overview": "data"})
|
|
505
|
+
expected_url = "https://api.llama.fi/overview/dexs"
|
|
506
|
+
expected_params = {
|
|
507
|
+
"excludeTotalDataChart": "true",
|
|
508
|
+
"excludeTotalDataChartBreakdown": "true",
|
|
509
|
+
"dataType": "dailyVolume",
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
513
|
+
client_instance = AsyncMock()
|
|
514
|
+
client_instance.get.return_value = dummy
|
|
515
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
516
|
+
result = await fetch_dex_overview()
|
|
517
|
+
client_instance.get.assert_called_once_with(
|
|
518
|
+
expected_url, params=expected_params
|
|
519
|
+
)
|
|
520
|
+
self.assertEqual(result, {"overview": "data"})
|
|
521
|
+
|
|
522
|
+
async def test_fetch_dex_summary_success(self):
|
|
523
|
+
protocol = "uniswap"
|
|
524
|
+
dummy = DummyResponse(200, {"summary": "data"})
|
|
525
|
+
expected_url = f"https://api.llama.fi/summary/dexs/{protocol}"
|
|
526
|
+
expected_params = {
|
|
527
|
+
"excludeTotalDataChart": "true",
|
|
528
|
+
"excludeTotalDataChartBreakdown": "true",
|
|
529
|
+
"dataType": "dailyVolume",
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
533
|
+
client_instance = AsyncMock()
|
|
534
|
+
client_instance.get.return_value = dummy
|
|
535
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
536
|
+
result = await fetch_dex_summary(protocol)
|
|
537
|
+
client_instance.get.assert_called_once_with(
|
|
538
|
+
expected_url, params=expected_params
|
|
539
|
+
)
|
|
540
|
+
self.assertEqual(result, {"summary": "data"})
|
|
541
|
+
|
|
542
|
+
async def test_fetch_options_overview_success(self):
|
|
543
|
+
dummy = DummyResponse(200, {"options": "data"})
|
|
544
|
+
expected_url = "https://api.llama.fi/overview/options"
|
|
545
|
+
expected_params = {
|
|
546
|
+
"excludeTotalDataChart": "true",
|
|
547
|
+
"excludeTotalDataChartBreakdown": "true",
|
|
548
|
+
"dataType": "dailyPremiumVolume",
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
552
|
+
client_instance = AsyncMock()
|
|
553
|
+
client_instance.get.return_value = dummy
|
|
554
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
555
|
+
result = await fetch_options_overview()
|
|
556
|
+
client_instance.get.assert_called_once_with(
|
|
557
|
+
expected_url, params=expected_params
|
|
558
|
+
)
|
|
559
|
+
self.assertEqual(result, {"options": "data"})
|
|
560
|
+
|
|
561
|
+
# --- Tests for Fees API ---
|
|
562
|
+
async def test_fetch_fees_overview_success(self):
|
|
563
|
+
dummy = DummyResponse(200, {"fees": "data"})
|
|
564
|
+
expected_url = "https://api.llama.fi/overview/fees"
|
|
565
|
+
expected_params = {
|
|
566
|
+
"excludeTotalDataChart": "true",
|
|
567
|
+
"excludeTotalDataChartBreakdown": "true",
|
|
568
|
+
"dataType": "dailyFees",
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
with patch("httpx.AsyncClient") as MockClient:
|
|
572
|
+
client_instance = AsyncMock()
|
|
573
|
+
client_instance.get.return_value = dummy
|
|
574
|
+
MockClient.return_value.__aenter__.return_value = client_instance
|
|
575
|
+
result = await fetch_fees_overview()
|
|
576
|
+
client_instance.get.assert_called_once_with(
|
|
577
|
+
expected_url, params=expected_params
|
|
578
|
+
)
|
|
579
|
+
self.assertEqual(result, {"fees": "data"})
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
if __name__ == "__main__":
|
|
583
|
+
unittest.main()
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Tool for fetching chain historical TVL via DeFiLlama API."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Type
|
|
4
|
+
|
|
5
|
+
from langchain.schema.runnable import RunnableConfig
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from intentkit.skills.defillama.api import fetch_chain_historical_tvl
|
|
9
|
+
from intentkit.skills.defillama.base import DefiLlamaBaseTool
|
|
10
|
+
|
|
11
|
+
FETCH_HISTORICAL_TVL_PROMPT = """
|
|
12
|
+
This tool fetches historical Total Value Locked (TVL) data for a specific blockchain.
|
|
13
|
+
Provide the chain name (e.g., "ethereum", "solana") to get its TVL history.
|
|
14
|
+
Returns a time series of TVL values with their corresponding dates.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HistoricalTVLDataPoint(BaseModel):
|
|
19
|
+
"""Model representing a single TVL data point."""
|
|
20
|
+
|
|
21
|
+
date: int = Field(..., description="Unix timestamp of the TVL measurement")
|
|
22
|
+
tvl: float = Field(..., description="Total Value Locked in USD at this timestamp")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FetchChainHistoricalTVLInput(BaseModel):
|
|
26
|
+
"""Input schema for fetching chain-specific historical TVL data."""
|
|
27
|
+
|
|
28
|
+
chain: str = Field(
|
|
29
|
+
..., description="Chain name to fetch TVL for (e.g., 'ethereum', 'solana')"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FetchChainHistoricalTVLResponse(BaseModel):
|
|
34
|
+
"""Response schema for chain-specific historical TVL data."""
|
|
35
|
+
|
|
36
|
+
chain: str = Field(..., description="Normalized chain name")
|
|
37
|
+
data: List[HistoricalTVLDataPoint] = Field(
|
|
38
|
+
default_factory=list, description="List of historical TVL data points"
|
|
39
|
+
)
|
|
40
|
+
error: str | None = Field(default=None, description="Error message if any")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DefiLlamaFetchChainHistoricalTvl(DefiLlamaBaseTool):
|
|
44
|
+
"""Tool for fetching historical TVL data for a specific blockchain.
|
|
45
|
+
|
|
46
|
+
This tool fetches the complete Total Value Locked (TVL) history for a given
|
|
47
|
+
blockchain using the DeFiLlama API. It includes rate limiting and chain
|
|
48
|
+
validation to ensure reliable data retrieval.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
tvl_tool = DefiLlamaFetchChainHistoricalTvl(
|
|
52
|
+
skill_store=store,
|
|
53
|
+
agent_id="agent_123",
|
|
54
|
+
agent_store=agent_store
|
|
55
|
+
)
|
|
56
|
+
result = await tvl_tool._arun(chain="ethereum")
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
name: str = "defillama_fetch_chain_historical_tvl"
|
|
60
|
+
description: str = FETCH_HISTORICAL_TVL_PROMPT
|
|
61
|
+
args_schema: Type[BaseModel] = FetchChainHistoricalTVLInput
|
|
62
|
+
|
|
63
|
+
async def _arun(
|
|
64
|
+
self, config: RunnableConfig, chain: str
|
|
65
|
+
) -> FetchChainHistoricalTVLResponse:
|
|
66
|
+
"""Fetch historical TVL data for the given chain.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
config: Runnable configuration
|
|
70
|
+
chain: Blockchain name (e.g., "ethereum", "solana")
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
FetchChainHistoricalTVLResponse containing chain name, TVL history or error
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
# Check rate limiting
|
|
77
|
+
context = self.context_from_config(config)
|
|
78
|
+
is_rate_limited, error_msg = await self.check_rate_limit(context)
|
|
79
|
+
if is_rate_limited:
|
|
80
|
+
return FetchChainHistoricalTVLResponse(chain=chain, error=error_msg)
|
|
81
|
+
|
|
82
|
+
# Validate chain parameter
|
|
83
|
+
is_valid, normalized_chain = await self.validate_chain(chain)
|
|
84
|
+
if not is_valid or normalized_chain is None:
|
|
85
|
+
return FetchChainHistoricalTVLResponse(
|
|
86
|
+
chain=chain, error=f"Invalid chain: {chain}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Fetch TVL history from API
|
|
90
|
+
result = await fetch_chain_historical_tvl(normalized_chain)
|
|
91
|
+
|
|
92
|
+
# Check for API errors
|
|
93
|
+
if isinstance(result, dict) and "error" in result:
|
|
94
|
+
return FetchChainHistoricalTVLResponse(
|
|
95
|
+
chain=normalized_chain, error=result["error"]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Parse response into our schema
|
|
99
|
+
data_points = [HistoricalTVLDataPoint(**point) for point in result]
|
|
100
|
+
|
|
101
|
+
return FetchChainHistoricalTVLResponse(
|
|
102
|
+
chain=normalized_chain, data=data_points
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return FetchChainHistoricalTVLResponse(chain=chain, error=str(e))
|