intentkit 0.6.13.dev2__py3-none-any.whl → 0.8.17__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 +1 -1
- intentkit/abstracts/agent.py +4 -5
- intentkit/abstracts/engine.py +5 -5
- intentkit/abstracts/graph.py +14 -7
- intentkit/abstracts/skill.py +6 -144
- intentkit/abstracts/twitter.py +4 -5
- intentkit/clients/__init__.py +5 -2
- intentkit/clients/cdp.py +101 -141
- intentkit/clients/twitter.py +83 -62
- intentkit/clients/web3.py +29 -0
- intentkit/config/config.py +8 -5
- intentkit/core/agent.py +472 -195
- intentkit/core/asset.py +253 -0
- intentkit/core/chat.py +51 -0
- intentkit/core/client.py +1 -1
- intentkit/core/credit.py +460 -130
- intentkit/core/engine.py +262 -233
- intentkit/core/node.py +15 -16
- intentkit/core/prompt.py +62 -28
- intentkit/core/scheduler.py +92 -0
- intentkit/core/statistics.py +168 -0
- intentkit/models/agent.py +1096 -949
- intentkit/models/agent_data.py +68 -38
- intentkit/models/agent_public.json +98 -0
- intentkit/models/agent_schema.json +54 -439
- intentkit/models/app_setting.py +96 -33
- intentkit/models/chat.py +74 -27
- intentkit/models/conversation.py +8 -8
- intentkit/models/credit.py +362 -74
- intentkit/models/db.py +26 -8
- intentkit/models/db_mig.py +2 -2
- intentkit/models/llm.csv +28 -0
- intentkit/models/llm.py +185 -350
- intentkit/models/redis.py +6 -4
- intentkit/models/skill.py +186 -72
- intentkit/models/skills.csv +174 -0
- intentkit/models/user.py +82 -24
- intentkit/skills/acolyt/__init__.py +2 -9
- intentkit/skills/acolyt/ask.py +3 -4
- intentkit/skills/acolyt/base.py +4 -9
- intentkit/skills/acolyt/schema.json +4 -3
- intentkit/skills/aixbt/__init__.py +2 -13
- intentkit/skills/aixbt/base.py +1 -7
- intentkit/skills/aixbt/projects.py +14 -15
- intentkit/skills/aixbt/schema.json +4 -4
- intentkit/skills/allora/__init__.py +2 -9
- intentkit/skills/allora/base.py +4 -9
- intentkit/skills/allora/price.py +3 -4
- intentkit/skills/allora/schema.json +3 -2
- intentkit/skills/base.py +248 -85
- intentkit/skills/basename/__init__.py +51 -0
- intentkit/skills/basename/base.py +11 -0
- intentkit/skills/basename/basename.svg +11 -0
- intentkit/skills/basename/schema.json +58 -0
- intentkit/skills/carv/__init__.py +115 -121
- intentkit/skills/carv/base.py +184 -185
- intentkit/skills/carv/fetch_news.py +3 -3
- intentkit/skills/carv/onchain_query.py +4 -4
- intentkit/skills/carv/schema.json +134 -137
- intentkit/skills/carv/token_info_and_price.py +5 -5
- intentkit/skills/casino/README.md +254 -0
- intentkit/skills/casino/__init__.py +86 -0
- intentkit/skills/casino/base.py +17 -0
- intentkit/skills/casino/casino.png +0 -0
- intentkit/skills/casino/deck_draw.py +127 -0
- intentkit/skills/casino/deck_shuffle.py +118 -0
- intentkit/skills/casino/dice_roll.py +100 -0
- intentkit/skills/casino/schema.json +77 -0
- intentkit/skills/casino/utils.py +107 -0
- intentkit/skills/cdp/__init__.py +22 -84
- intentkit/skills/cdp/base.py +1 -7
- intentkit/skills/cdp/schema.json +11 -314
- intentkit/skills/chainlist/__init__.py +2 -7
- intentkit/skills/chainlist/base.py +1 -7
- intentkit/skills/chainlist/chain_lookup.py +18 -18
- intentkit/skills/chainlist/schema.json +3 -5
- intentkit/skills/common/__init__.py +2 -9
- intentkit/skills/common/base.py +1 -7
- intentkit/skills/common/current_time.py +1 -2
- intentkit/skills/common/schema.json +2 -2
- intentkit/skills/cookiefun/__init__.py +6 -9
- intentkit/skills/cookiefun/base.py +2 -7
- intentkit/skills/cookiefun/get_account_details.py +7 -7
- intentkit/skills/cookiefun/get_account_feed.py +19 -19
- intentkit/skills/cookiefun/get_account_smart_followers.py +7 -7
- intentkit/skills/cookiefun/get_sectors.py +3 -3
- intentkit/skills/cookiefun/schema.json +1 -3
- intentkit/skills/cookiefun/search_accounts.py +9 -9
- intentkit/skills/cryptocompare/__init__.py +7 -24
- intentkit/skills/cryptocompare/api.py +2 -3
- intentkit/skills/cryptocompare/base.py +11 -25
- intentkit/skills/cryptocompare/fetch_news.py +4 -5
- intentkit/skills/cryptocompare/fetch_price.py +6 -7
- intentkit/skills/cryptocompare/fetch_top_exchanges.py +4 -5
- intentkit/skills/cryptocompare/fetch_top_market_cap.py +4 -5
- intentkit/skills/cryptocompare/fetch_top_volume.py +4 -5
- intentkit/skills/cryptocompare/fetch_trading_signals.py +5 -6
- intentkit/skills/cryptocompare/schema.json +3 -3
- intentkit/skills/cryptopanic/__init__.py +7 -10
- intentkit/skills/cryptopanic/base.py +51 -55
- intentkit/skills/cryptopanic/fetch_crypto_news.py +4 -8
- intentkit/skills/cryptopanic/fetch_crypto_sentiment.py +5 -7
- intentkit/skills/cryptopanic/schema.json +105 -103
- intentkit/skills/dapplooker/__init__.py +2 -9
- intentkit/skills/dapplooker/base.py +4 -9
- intentkit/skills/dapplooker/dapplooker_token_data.py +7 -7
- intentkit/skills/dapplooker/schema.json +3 -5
- intentkit/skills/defillama/__init__.py +24 -74
- intentkit/skills/defillama/api.py +6 -9
- intentkit/skills/defillama/base.py +11 -21
- intentkit/skills/defillama/coins/fetch_batch_historical_prices.py +8 -10
- intentkit/skills/defillama/coins/fetch_block.py +6 -8
- intentkit/skills/defillama/coins/fetch_current_prices.py +8 -10
- intentkit/skills/defillama/coins/fetch_first_price.py +7 -9
- intentkit/skills/defillama/coins/fetch_historical_prices.py +9 -11
- intentkit/skills/defillama/coins/fetch_price_chart.py +9 -11
- intentkit/skills/defillama/coins/fetch_price_percentage.py +7 -9
- intentkit/skills/defillama/config/chains.py +1 -3
- intentkit/skills/defillama/fees/fetch_fees_overview.py +24 -26
- intentkit/skills/defillama/schema.json +5 -1
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_chains.py +16 -18
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_charts.py +8 -10
- intentkit/skills/defillama/stablecoins/fetch_stablecoin_prices.py +5 -7
- intentkit/skills/defillama/stablecoins/fetch_stablecoins.py +7 -9
- intentkit/skills/defillama/tests/api_integration.test.py +1 -1
- intentkit/skills/defillama/tvl/fetch_chain_historical_tvl.py +4 -6
- intentkit/skills/defillama/tvl/fetch_chains.py +9 -11
- intentkit/skills/defillama/tvl/fetch_historical_tvl.py +4 -6
- intentkit/skills/defillama/tvl/fetch_protocol.py +32 -38
- intentkit/skills/defillama/tvl/fetch_protocol_current_tvl.py +3 -5
- intentkit/skills/defillama/tvl/fetch_protocols.py +37 -45
- intentkit/skills/defillama/volumes/fetch_dex_overview.py +42 -48
- intentkit/skills/defillama/volumes/fetch_dex_summary.py +35 -37
- intentkit/skills/defillama/volumes/fetch_options_overview.py +24 -28
- intentkit/skills/defillama/yields/fetch_pool_chart.py +10 -12
- intentkit/skills/defillama/yields/fetch_pools.py +26 -30
- intentkit/skills/dexscreener/README.md +154 -0
- intentkit/skills/dexscreener/__init__.py +97 -93
- intentkit/skills/dexscreener/base.py +125 -133
- intentkit/skills/dexscreener/get_pair_info.py +158 -0
- intentkit/skills/dexscreener/get_token_pairs.py +165 -0
- intentkit/skills/dexscreener/get_tokens_info.py +212 -0
- intentkit/skills/dexscreener/model/search_token_response.py +80 -82
- intentkit/skills/dexscreener/schema.json +91 -48
- intentkit/skills/dexscreener/search_token.py +182 -321
- intentkit/skills/dexscreener/utils.py +420 -0
- intentkit/skills/dune_analytics/__init__.py +7 -9
- intentkit/skills/dune_analytics/base.py +48 -52
- intentkit/skills/dune_analytics/fetch_kol_buys.py +5 -7
- intentkit/skills/dune_analytics/fetch_nation_metrics.py +6 -8
- intentkit/skills/dune_analytics/schema.json +104 -99
- intentkit/skills/elfa/__init__.py +5 -18
- intentkit/skills/elfa/base.py +10 -14
- intentkit/skills/elfa/mention.py +19 -21
- intentkit/skills/elfa/schema.json +3 -2
- intentkit/skills/elfa/stats.py +4 -4
- intentkit/skills/elfa/tokens.py +12 -12
- intentkit/skills/elfa/utils.py +26 -28
- intentkit/skills/enso/__init__.py +11 -31
- intentkit/skills/enso/base.py +50 -35
- intentkit/skills/enso/best_yield.py +16 -24
- intentkit/skills/enso/networks.py +6 -11
- intentkit/skills/enso/prices.py +11 -13
- intentkit/skills/enso/route.py +34 -38
- intentkit/skills/enso/schema.json +3 -2
- intentkit/skills/enso/tokens.py +29 -38
- intentkit/skills/enso/wallet.py +76 -191
- intentkit/skills/erc20/__init__.py +50 -0
- intentkit/skills/erc20/base.py +11 -0
- intentkit/skills/erc20/erc20.svg +5 -0
- intentkit/skills/erc20/schema.json +74 -0
- intentkit/skills/erc721/__init__.py +53 -0
- intentkit/skills/erc721/base.py +11 -0
- intentkit/skills/erc721/erc721.svg +5 -0
- intentkit/skills/erc721/schema.json +90 -0
- intentkit/skills/firecrawl/README.md +11 -5
- intentkit/skills/firecrawl/__init__.py +5 -18
- intentkit/skills/firecrawl/base.py +4 -11
- intentkit/skills/firecrawl/clear.py +4 -8
- intentkit/skills/firecrawl/crawl.py +19 -19
- intentkit/skills/firecrawl/query.py +4 -3
- intentkit/skills/firecrawl/schema.json +6 -8
- intentkit/skills/firecrawl/scrape.py +150 -40
- intentkit/skills/firecrawl/utils.py +50 -42
- intentkit/skills/github/__init__.py +2 -7
- intentkit/skills/github/base.py +1 -7
- intentkit/skills/github/github_search.py +1 -2
- intentkit/skills/github/schema.json +3 -4
- intentkit/skills/heurist/__init__.py +8 -27
- intentkit/skills/heurist/base.py +4 -9
- intentkit/skills/heurist/image_generation_animagine_xl.py +12 -13
- intentkit/skills/heurist/image_generation_arthemy_comics.py +12 -13
- intentkit/skills/heurist/image_generation_arthemy_real.py +12 -13
- intentkit/skills/heurist/image_generation_braindance.py +12 -13
- intentkit/skills/heurist/image_generation_cyber_realistic_xl.py +12 -13
- intentkit/skills/heurist/image_generation_flux_1_dev.py +12 -13
- intentkit/skills/heurist/image_generation_sdxl.py +12 -13
- intentkit/skills/heurist/schema.json +2 -2
- intentkit/skills/http/__init__.py +4 -15
- intentkit/skills/http/base.py +1 -7
- intentkit/skills/http/get.py +21 -16
- intentkit/skills/http/post.py +23 -18
- intentkit/skills/http/put.py +23 -18
- intentkit/skills/http/schema.json +4 -5
- intentkit/skills/lifi/__init__.py +8 -13
- intentkit/skills/lifi/base.py +1 -7
- intentkit/skills/lifi/schema.json +17 -8
- intentkit/skills/lifi/token_execute.py +36 -30
- intentkit/skills/lifi/token_quote.py +8 -10
- intentkit/skills/lifi/utils.py +104 -51
- intentkit/skills/moralis/__init__.py +6 -10
- intentkit/skills/moralis/api.py +6 -7
- intentkit/skills/moralis/base.py +5 -10
- intentkit/skills/moralis/fetch_chain_portfolio.py +10 -11
- intentkit/skills/moralis/fetch_nft_portfolio.py +22 -22
- intentkit/skills/moralis/fetch_solana_portfolio.py +11 -12
- intentkit/skills/moralis/fetch_wallet_portfolio.py +8 -9
- intentkit/skills/moralis/schema.json +7 -2
- intentkit/skills/morpho/__init__.py +52 -0
- intentkit/skills/morpho/base.py +11 -0
- intentkit/skills/morpho/morpho.svg +12 -0
- intentkit/skills/morpho/schema.json +73 -0
- intentkit/skills/nation/__init__.py +4 -9
- intentkit/skills/nation/base.py +5 -10
- intentkit/skills/nation/nft_check.py +3 -4
- intentkit/skills/nation/schema.json +4 -3
- intentkit/skills/onchain.py +23 -0
- intentkit/skills/openai/__init__.py +17 -18
- intentkit/skills/openai/base.py +10 -14
- intentkit/skills/openai/dalle_image_generation.py +3 -8
- intentkit/skills/openai/gpt_avatar_generator.py +102 -0
- intentkit/skills/openai/gpt_image_generation.py +4 -8
- intentkit/skills/openai/gpt_image_mini_generator.py +91 -0
- intentkit/skills/openai/gpt_image_to_image.py +4 -8
- intentkit/skills/openai/image_to_text.py +3 -7
- intentkit/skills/openai/schema.json +34 -3
- intentkit/skills/portfolio/__init__.py +11 -35
- intentkit/skills/portfolio/base.py +33 -19
- intentkit/skills/portfolio/schema.json +3 -5
- intentkit/skills/portfolio/token_balances.py +21 -21
- intentkit/skills/portfolio/wallet_approvals.py +17 -18
- intentkit/skills/portfolio/wallet_defi_positions.py +3 -3
- intentkit/skills/portfolio/wallet_history.py +31 -31
- intentkit/skills/portfolio/wallet_net_worth.py +13 -13
- intentkit/skills/portfolio/wallet_nfts.py +19 -19
- intentkit/skills/portfolio/wallet_profitability.py +18 -18
- intentkit/skills/portfolio/wallet_profitability_summary.py +5 -5
- intentkit/skills/portfolio/wallet_stats.py +3 -3
- intentkit/skills/portfolio/wallet_swaps.py +19 -19
- intentkit/skills/pyth/__init__.py +50 -0
- intentkit/skills/pyth/base.py +11 -0
- intentkit/skills/pyth/pyth.svg +6 -0
- intentkit/skills/pyth/schema.json +75 -0
- intentkit/skills/skills.toml +40 -0
- intentkit/skills/slack/__init__.py +5 -17
- intentkit/skills/slack/base.py +3 -9
- intentkit/skills/slack/get_channel.py +8 -8
- intentkit/skills/slack/get_message.py +9 -9
- intentkit/skills/slack/schedule_message.py +5 -5
- intentkit/skills/slack/schema.json +2 -2
- intentkit/skills/slack/send_message.py +3 -5
- intentkit/skills/supabase/__init__.py +7 -23
- intentkit/skills/supabase/base.py +9 -13
- intentkit/skills/supabase/delete_data.py +5 -6
- intentkit/skills/supabase/fetch_data.py +13 -14
- intentkit/skills/supabase/insert_data.py +5 -6
- intentkit/skills/supabase/invoke_function.py +7 -8
- intentkit/skills/supabase/schema.json +2 -3
- intentkit/skills/supabase/update_data.py +7 -8
- intentkit/skills/supabase/upsert_data.py +5 -6
- intentkit/skills/superfluid/__init__.py +53 -0
- intentkit/skills/superfluid/base.py +11 -0
- intentkit/skills/superfluid/schema.json +89 -0
- intentkit/skills/superfluid/superfluid.svg +6 -0
- intentkit/skills/system/__init__.py +7 -24
- intentkit/skills/system/add_autonomous_task.py +10 -12
- intentkit/skills/system/delete_autonomous_task.py +2 -2
- intentkit/skills/system/edit_autonomous_task.py +14 -18
- intentkit/skills/system/list_autonomous_tasks.py +3 -5
- intentkit/skills/system/read_agent_api_key.py +6 -4
- intentkit/skills/system/regenerate_agent_api_key.py +6 -4
- intentkit/skills/system/schema.json +6 -8
- intentkit/skills/tavily/__init__.py +3 -12
- intentkit/skills/tavily/base.py +4 -9
- intentkit/skills/tavily/schema.json +3 -5
- intentkit/skills/tavily/tavily_extract.py +2 -4
- intentkit/skills/tavily/tavily_search.py +4 -6
- intentkit/skills/token/__init__.py +5 -10
- intentkit/skills/token/base.py +7 -11
- intentkit/skills/token/erc20_transfers.py +19 -19
- intentkit/skills/token/schema.json +3 -6
- intentkit/skills/token/token_analytics.py +3 -3
- intentkit/skills/token/token_price.py +13 -13
- intentkit/skills/token/token_search.py +9 -9
- intentkit/skills/twitter/__init__.py +11 -35
- intentkit/skills/twitter/base.py +23 -35
- intentkit/skills/twitter/follow_user.py +3 -7
- intentkit/skills/twitter/get_mentions.py +6 -13
- intentkit/skills/twitter/get_timeline.py +5 -13
- intentkit/skills/twitter/get_user_by_username.py +3 -7
- intentkit/skills/twitter/get_user_tweets.py +6 -14
- intentkit/skills/twitter/like_tweet.py +3 -7
- intentkit/skills/twitter/post_tweet.py +23 -12
- intentkit/skills/twitter/reply_tweet.py +21 -12
- intentkit/skills/twitter/retweet.py +3 -7
- intentkit/skills/twitter/schema.json +1 -0
- intentkit/skills/twitter/search_tweets.py +5 -13
- intentkit/skills/unrealspeech/__init__.py +2 -7
- intentkit/skills/unrealspeech/base.py +2 -8
- intentkit/skills/unrealspeech/schema.json +2 -5
- intentkit/skills/unrealspeech/text_to_speech.py +8 -8
- intentkit/skills/venice_audio/__init__.py +98 -106
- intentkit/skills/venice_audio/base.py +117 -121
- intentkit/skills/venice_audio/input.py +41 -41
- intentkit/skills/venice_audio/schema.json +151 -152
- intentkit/skills/venice_audio/venice_audio.py +38 -21
- intentkit/skills/venice_image/__init__.py +147 -154
- intentkit/skills/venice_image/api.py +138 -138
- intentkit/skills/venice_image/base.py +185 -192
- intentkit/skills/venice_image/config.py +33 -35
- intentkit/skills/venice_image/image_enhance/image_enhance.py +2 -3
- intentkit/skills/venice_image/image_enhance/image_enhance_base.py +21 -23
- intentkit/skills/venice_image/image_enhance/image_enhance_input.py +38 -40
- intentkit/skills/venice_image/image_generation/image_generation_base.py +9 -9
- intentkit/skills/venice_image/image_generation/image_generation_fluently_xl.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev.py +27 -27
- intentkit/skills/venice_image/image_generation/image_generation_flux_dev_uncensored.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_input.py +158 -158
- intentkit/skills/venice_image/image_generation/image_generation_lustify_sdxl.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_pony_realism.py +26 -26
- intentkit/skills/venice_image/image_generation/image_generation_stable_diffusion_3_5.py +28 -28
- intentkit/skills/venice_image/image_generation/image_generation_venice_sd35.py +28 -28
- intentkit/skills/venice_image/image_upscale/image_upscale.py +3 -3
- intentkit/skills/venice_image/image_upscale/image_upscale_base.py +21 -23
- intentkit/skills/venice_image/image_upscale/image_upscale_input.py +22 -22
- intentkit/skills/venice_image/image_vision/image_vision.py +2 -2
- intentkit/skills/venice_image/image_vision/image_vision_base.py +17 -17
- intentkit/skills/venice_image/image_vision/image_vision_input.py +9 -9
- intentkit/skills/venice_image/schema.json +267 -267
- intentkit/skills/venice_image/utils.py +77 -78
- intentkit/skills/web_scraper/__init__.py +5 -18
- intentkit/skills/web_scraper/base.py +21 -7
- intentkit/skills/web_scraper/document_indexer.py +7 -6
- intentkit/skills/web_scraper/schema.json +2 -6
- intentkit/skills/web_scraper/scrape_and_index.py +15 -15
- intentkit/skills/web_scraper/utils.py +62 -63
- intentkit/skills/web_scraper/website_indexer.py +17 -19
- intentkit/skills/weth/__init__.py +49 -0
- intentkit/skills/weth/base.py +11 -0
- intentkit/skills/weth/schema.json +58 -0
- intentkit/skills/weth/weth.svg +6 -0
- intentkit/skills/wow/__init__.py +51 -0
- intentkit/skills/wow/base.py +11 -0
- intentkit/skills/wow/schema.json +89 -0
- intentkit/skills/wow/wow.svg +7 -0
- intentkit/skills/x402/__init__.py +61 -0
- intentkit/skills/x402/ask_agent.py +98 -0
- intentkit/skills/x402/base.py +99 -0
- intentkit/skills/x402/http_request.py +117 -0
- intentkit/skills/x402/schema.json +45 -0
- intentkit/skills/x402/x402.webp +0 -0
- intentkit/skills/xmtp/__init__.py +4 -15
- intentkit/skills/xmtp/base.py +61 -2
- intentkit/skills/xmtp/price.py +18 -13
- intentkit/skills/xmtp/schema.json +69 -71
- intentkit/skills/xmtp/swap.py +22 -25
- intentkit/skills/xmtp/transfer.py +71 -32
- intentkit/utils/chain.py +3 -3
- intentkit/utils/error.py +14 -1
- intentkit/utils/logging.py +2 -4
- intentkit/utils/s3.py +59 -7
- intentkit/utils/schema.py +100 -0
- intentkit/utils/slack_alert.py +7 -8
- {intentkit-0.6.13.dev2.dist-info → intentkit-0.8.17.dist-info}/METADATA +14 -16
- intentkit-0.8.17.dist-info/RECORD +466 -0
- intentkit/abstracts/exception.py +0 -9
- intentkit/core/skill.py +0 -200
- intentkit/models/generator.py +0 -347
- intentkit/skills/cdp/get_balance.py +0 -110
- intentkit/skills/cdp/swap.py +0 -121
- intentkit/skills/moralis/tests/__init__.py +0 -0
- intentkit/skills/moralis/tests/test_wallet.py +0 -511
- intentkit-0.6.13.dev2.dist-info/RECORD +0 -409
- {intentkit-0.6.13.dev2.dist-info → intentkit-0.8.17.dist-info}/WHEEL +0 -0
- {intentkit-0.6.13.dev2.dist-info → intentkit-0.8.17.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions and constants for DexScreener skills.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import ValidationError
|
|
12
|
+
|
|
13
|
+
from intentkit.skills.dexscreener.model.search_token_response import PairModel
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# API Base URL
|
|
18
|
+
DEXSCREENER_BASE_URL = "https://api.dexscreener.com"
|
|
19
|
+
|
|
20
|
+
# API Endpoints
|
|
21
|
+
API_ENDPOINTS = {
|
|
22
|
+
"search": "/latest/dex/search",
|
|
23
|
+
"pairs": "/latest/dex/pairs",
|
|
24
|
+
"token_pairs": "/token-pairs/v1",
|
|
25
|
+
"tokens": "/tokens/v1",
|
|
26
|
+
"token_profiles": "/token-profiles/latest/v1",
|
|
27
|
+
"token_boosts_latest": "/token-boosts/latest/v1",
|
|
28
|
+
"token_boosts_top": "/token-boosts/top/v1",
|
|
29
|
+
"orders": "/orders/v1",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Rate Limits (requests per minute)
|
|
33
|
+
RATE_LIMITS = {
|
|
34
|
+
"search": 300,
|
|
35
|
+
"pairs": 300,
|
|
36
|
+
"token_pairs": 300,
|
|
37
|
+
"tokens": 300,
|
|
38
|
+
"token_profiles": 60,
|
|
39
|
+
"token_boosts": 60,
|
|
40
|
+
"orders": 60,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Limits
|
|
44
|
+
MAX_SEARCH_RESULTS = 25
|
|
45
|
+
MAX_TOKENS_BATCH = 30
|
|
46
|
+
|
|
47
|
+
# Common disclaimer for search results
|
|
48
|
+
SEARCH_DISCLAIMER = {
|
|
49
|
+
"disclaimer": (
|
|
50
|
+
"Search results may include unofficial, duplicate, or potentially malicious tokens. "
|
|
51
|
+
"If multiple unrelated tokens share a similar name or ticker, ask the user for the exact token address. "
|
|
52
|
+
"If the correct token is not found, re-run the tool using the provided address. "
|
|
53
|
+
"Also advise the user to verify the token's legitimacy via its official social links included in the result."
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Query Types
|
|
59
|
+
class QueryType(str, Enum):
|
|
60
|
+
TEXT = "TEXT"
|
|
61
|
+
TICKER = "TICKER"
|
|
62
|
+
ADDRESS = "ADDRESS"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Sort Options
|
|
66
|
+
class SortBy(str, Enum):
|
|
67
|
+
LIQUIDITY = "liquidity"
|
|
68
|
+
VOLUME = "volume"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Volume Timeframes
|
|
72
|
+
class VolumeTimeframe(str, Enum):
|
|
73
|
+
FIVE_MINUTES = "5_minutes"
|
|
74
|
+
ONE_HOUR = "1_hour"
|
|
75
|
+
SIX_HOUR = "6_hour"
|
|
76
|
+
TWENTY_FOUR_HOUR = "24_hour"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Supported Chain IDs
|
|
80
|
+
SUPPORTED_CHAINS = [
|
|
81
|
+
"ethereum",
|
|
82
|
+
"bsc",
|
|
83
|
+
"polygon",
|
|
84
|
+
"avalanche",
|
|
85
|
+
"fantom",
|
|
86
|
+
"cronos",
|
|
87
|
+
"arbitrum",
|
|
88
|
+
"optimism",
|
|
89
|
+
"base",
|
|
90
|
+
"solana",
|
|
91
|
+
"sui",
|
|
92
|
+
"tron",
|
|
93
|
+
"ton",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def determine_query_type(query: str) -> QueryType:
|
|
98
|
+
"""
|
|
99
|
+
Determine whether the query is a TEXT, TICKER, or ADDRESS.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
query: The search query string
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
QueryType enum value
|
|
106
|
+
"""
|
|
107
|
+
if query.startswith("0x"):
|
|
108
|
+
return QueryType.ADDRESS
|
|
109
|
+
if query.startswith("$"):
|
|
110
|
+
return QueryType.TICKER
|
|
111
|
+
return QueryType.TEXT
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_liquidity_value(pair: PairModel) -> float:
|
|
115
|
+
"""
|
|
116
|
+
Extract liquidity USD value from a pair, defaulting to 0.0 if not available.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
pair: PairModel instance
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Liquidity value in USD as float
|
|
123
|
+
"""
|
|
124
|
+
return (
|
|
125
|
+
pair.liquidity.usd if pair.liquidity and pair.liquidity.usd is not None else 0.0
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_volume_value(
|
|
130
|
+
pair: PairModel, timeframe: VolumeTimeframe = VolumeTimeframe.TWENTY_FOUR_HOUR
|
|
131
|
+
) -> float:
|
|
132
|
+
"""
|
|
133
|
+
Extract volume value from a pair for the specified timeframe.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
pair: PairModel instance
|
|
137
|
+
timeframe: VolumeTimeframe enum value
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Volume value as float
|
|
141
|
+
"""
|
|
142
|
+
if not pair.volume:
|
|
143
|
+
return 0.0
|
|
144
|
+
|
|
145
|
+
volume_map = {
|
|
146
|
+
VolumeTimeframe.FIVE_MINUTES: pair.volume.m5,
|
|
147
|
+
VolumeTimeframe.ONE_HOUR: pair.volume.h1,
|
|
148
|
+
VolumeTimeframe.SIX_HOUR: pair.volume.h6,
|
|
149
|
+
VolumeTimeframe.TWENTY_FOUR_HOUR: pair.volume.h24,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return volume_map.get(timeframe, 0.0) or 0.0
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_sort_function(
|
|
156
|
+
sort_by: SortBy,
|
|
157
|
+
volume_timeframe: VolumeTimeframe = VolumeTimeframe.TWENTY_FOUR_HOUR,
|
|
158
|
+
) -> Callable[[PairModel], float]:
|
|
159
|
+
"""
|
|
160
|
+
Get the appropriate sorting function based on sort criteria.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
sort_by: SortBy enum value
|
|
164
|
+
volume_timeframe: VolumeTimeframe enum value (used when sorting by volume)
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Callable function that takes a PairModel and returns a float for sorting
|
|
168
|
+
"""
|
|
169
|
+
if sort_by == SortBy.LIQUIDITY:
|
|
170
|
+
return get_liquidity_value
|
|
171
|
+
elif sort_by == SortBy.VOLUME:
|
|
172
|
+
return lambda pair: get_volume_value(pair, volume_timeframe)
|
|
173
|
+
else:
|
|
174
|
+
logger.warning(f"Invalid sort_by value '{sort_by}', defaulting to liquidity.")
|
|
175
|
+
return get_liquidity_value
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def sort_pairs_by_criteria(
|
|
179
|
+
pairs: list[PairModel],
|
|
180
|
+
sort_by: SortBy = SortBy.LIQUIDITY,
|
|
181
|
+
volume_timeframe: VolumeTimeframe = VolumeTimeframe.TWENTY_FOUR_HOUR,
|
|
182
|
+
reverse: bool = True,
|
|
183
|
+
) -> list[PairModel]:
|
|
184
|
+
"""
|
|
185
|
+
Sort pairs by the specified criteria.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
pairs: List of PairModel instances to sort
|
|
189
|
+
sort_by: Sorting criteria (liquidity or volume)
|
|
190
|
+
volume_timeframe: Timeframe for volume sorting
|
|
191
|
+
reverse: Sort in descending order if True
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Sorted list of PairModel instances
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
sort_func = get_sort_function(sort_by, volume_timeframe)
|
|
198
|
+
return sorted(pairs, key=sort_func, reverse=reverse)
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.error(f"Failed to sort pairs: {e}", exc_info=True)
|
|
201
|
+
return pairs # Return original list if sorting fails
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def filter_ticker_pairs(pairs: list[PairModel], target_ticker: str) -> list[PairModel]:
|
|
205
|
+
"""
|
|
206
|
+
Filter pairs to only include those where base token symbol matches target ticker.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
pairs: List of PairModel instances
|
|
210
|
+
target_ticker: Target ticker symbol (case-insensitive)
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Filtered list of PairModel instances
|
|
214
|
+
"""
|
|
215
|
+
target_ticker_upper = target_ticker.upper()
|
|
216
|
+
return [
|
|
217
|
+
p
|
|
218
|
+
for p in pairs
|
|
219
|
+
if p.baseToken
|
|
220
|
+
and p.baseToken.symbol
|
|
221
|
+
and p.baseToken.symbol.upper() == target_ticker_upper
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def filter_address_pairs(
|
|
226
|
+
pairs: list[PairModel], target_address: str
|
|
227
|
+
) -> list[PairModel]:
|
|
228
|
+
"""
|
|
229
|
+
Filter pairs to only include those matching the target address.
|
|
230
|
+
Checks pairAddress, baseToken.address, and quoteToken.address.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
pairs: List of PairModel instances
|
|
234
|
+
target_address: Target address (case-insensitive)
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Filtered list of PairModel instances
|
|
238
|
+
"""
|
|
239
|
+
target_address_lower = target_address.lower()
|
|
240
|
+
return [
|
|
241
|
+
p
|
|
242
|
+
for p in pairs
|
|
243
|
+
if (p.pairAddress and p.pairAddress.lower() == target_address_lower)
|
|
244
|
+
or (
|
|
245
|
+
p.baseToken
|
|
246
|
+
and p.baseToken.address
|
|
247
|
+
and p.baseToken.address.lower() == target_address_lower
|
|
248
|
+
)
|
|
249
|
+
or (
|
|
250
|
+
p.quoteToken
|
|
251
|
+
and p.quoteToken.address
|
|
252
|
+
and p.quoteToken.address.lower() == target_address_lower
|
|
253
|
+
)
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def create_error_response(
|
|
258
|
+
error_type: str,
|
|
259
|
+
message: str,
|
|
260
|
+
details: str | None = None,
|
|
261
|
+
additional_data: dict[str, Any] | None = None,
|
|
262
|
+
) -> str:
|
|
263
|
+
"""
|
|
264
|
+
Create a standardized error response in JSON format.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
error_type: Type/category of error
|
|
268
|
+
message: Human-readable error message
|
|
269
|
+
details: Optional additional details about the error
|
|
270
|
+
additional_data: Optional dictionary of additional data to include
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
JSON string containing error information
|
|
274
|
+
"""
|
|
275
|
+
response = {
|
|
276
|
+
"error": message,
|
|
277
|
+
"error_type": error_type,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if details:
|
|
281
|
+
response["details"] = details
|
|
282
|
+
|
|
283
|
+
if additional_data:
|
|
284
|
+
response.update(additional_data)
|
|
285
|
+
|
|
286
|
+
return json.dumps(response, indent=2)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def create_no_results_response(
|
|
290
|
+
query_info: str,
|
|
291
|
+
reason: str = "no results found",
|
|
292
|
+
additional_data: dict[str, Any] | None = None,
|
|
293
|
+
) -> str:
|
|
294
|
+
"""
|
|
295
|
+
Create a standardized "no results found" response.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
query_info: Information about the query that was performed
|
|
299
|
+
reason: Reason why no results were found
|
|
300
|
+
additional_data: Optional additional data to include
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
JSON string containing no results information
|
|
304
|
+
"""
|
|
305
|
+
response = {
|
|
306
|
+
"message": f"No results found for the query. Reason: {reason}.",
|
|
307
|
+
"query_info": query_info,
|
|
308
|
+
"pairs": [],
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if additional_data:
|
|
312
|
+
response.update(additional_data)
|
|
313
|
+
|
|
314
|
+
return json.dumps(response, indent=2)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def handle_validation_error(
|
|
318
|
+
error: ValidationError, query_info: str, data_length: int | None = None
|
|
319
|
+
) -> str:
|
|
320
|
+
"""
|
|
321
|
+
Handle validation errors in a standardized way.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
error: The ValidationError that occurred
|
|
325
|
+
query_info: Information about the query being processed
|
|
326
|
+
data_length: Optional length of the data that failed validation
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
JSON error response string
|
|
330
|
+
"""
|
|
331
|
+
log_message = f"Failed to validate DexScreener response structure for {query_info}. Error: {error}"
|
|
332
|
+
if data_length:
|
|
333
|
+
log_message += f". Raw data length: {data_length}"
|
|
334
|
+
|
|
335
|
+
logger.error(log_message, exc_info=True)
|
|
336
|
+
|
|
337
|
+
return create_error_response(
|
|
338
|
+
error_type="validation_error",
|
|
339
|
+
message="Failed to parse successful DexScreener API response",
|
|
340
|
+
details=str(error.errors()),
|
|
341
|
+
additional_data={"query_info": query_info},
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def truncate_large_fields(
|
|
346
|
+
data: dict[str, Any], max_length: int = 500
|
|
347
|
+
) -> dict[str, Any]:
|
|
348
|
+
"""
|
|
349
|
+
Truncate large string fields in error response data to avoid overwhelming the LLM.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
data: Dictionary potentially containing large string fields
|
|
353
|
+
max_length: Maximum length for string fields before truncation
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Dictionary with truncated fields
|
|
357
|
+
"""
|
|
358
|
+
truncated = data.copy()
|
|
359
|
+
|
|
360
|
+
for key in ["details", "response_body"]:
|
|
361
|
+
if isinstance(truncated.get(key), str) and len(truncated[key]) > max_length:
|
|
362
|
+
truncated[key] = truncated[key][:max_length] + "... (truncated)"
|
|
363
|
+
|
|
364
|
+
return truncated
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def group_pairs_by_token(pairs: list[PairModel]) -> dict[str, list[PairModel]]:
|
|
368
|
+
"""
|
|
369
|
+
Group pairs by token address for better organization in multi-token responses.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
pairs: List of PairModel instances
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Dictionary mapping lowercase token addresses to lists of pairs
|
|
376
|
+
"""
|
|
377
|
+
tokens_data = {}
|
|
378
|
+
|
|
379
|
+
for pair in pairs:
|
|
380
|
+
# Group by base token address
|
|
381
|
+
if pair.baseToken and pair.baseToken.address:
|
|
382
|
+
base_addr = pair.baseToken.address.lower()
|
|
383
|
+
if base_addr not in tokens_data:
|
|
384
|
+
tokens_data[base_addr] = []
|
|
385
|
+
tokens_data[base_addr].append(pair)
|
|
386
|
+
|
|
387
|
+
# Group by quote token address
|
|
388
|
+
if pair.quoteToken and pair.quoteToken.address:
|
|
389
|
+
quote_addr = pair.quoteToken.address.lower()
|
|
390
|
+
if quote_addr not in tokens_data:
|
|
391
|
+
tokens_data[quote_addr] = []
|
|
392
|
+
tokens_data[quote_addr].append(pair)
|
|
393
|
+
|
|
394
|
+
return tokens_data
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def validate_chain_id(chain_id: str) -> bool:
|
|
398
|
+
"""
|
|
399
|
+
Validate if the chain ID is supported.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
chain_id: Chain ID to validate
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
True if chain ID is supported, False otherwise
|
|
406
|
+
"""
|
|
407
|
+
return chain_id.lower() in SUPPORTED_CHAINS
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def format_success_response(data: dict[str, Any]) -> str:
|
|
411
|
+
"""
|
|
412
|
+
Format a successful response as JSON string.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
data: Response data dictionary
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
JSON formatted string
|
|
419
|
+
"""
|
|
420
|
+
return json.dumps(data, indent=2)
|
|
@@ -4,16 +4,15 @@ Loads and initializes skills for fetching data from Dune Analytics API.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import TypedDict
|
|
8
8
|
|
|
9
|
-
from intentkit.abstracts.skill import SkillStoreABC
|
|
10
9
|
from intentkit.skills.base import SkillConfig, SkillState
|
|
11
10
|
from intentkit.skills.dune_analytics.base import DuneBaseTool
|
|
12
11
|
|
|
13
12
|
logger = logging.getLogger(__name__)
|
|
14
13
|
|
|
15
14
|
# Cache for skill instances
|
|
16
|
-
_skill_cache:
|
|
15
|
+
_skill_cache: dict[str, DuneBaseTool] = {}
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class SkillStates(TypedDict):
|
|
@@ -33,9 +32,8 @@ class Config(SkillConfig):
|
|
|
33
32
|
async def get_skills(
|
|
34
33
|
config: Config,
|
|
35
34
|
is_private: bool,
|
|
36
|
-
store: SkillStoreABC,
|
|
37
35
|
**kwargs,
|
|
38
|
-
) ->
|
|
36
|
+
) -> list[DuneBaseTool]:
|
|
39
37
|
"""Load Dune Analytics skills based on configuration.
|
|
40
38
|
|
|
41
39
|
Args:
|
|
@@ -59,7 +57,7 @@ async def get_skills(
|
|
|
59
57
|
|
|
60
58
|
loaded_skills = []
|
|
61
59
|
for name in available_skills:
|
|
62
|
-
skill = get_dune_skill(name
|
|
60
|
+
skill = get_dune_skill(name)
|
|
63
61
|
if skill:
|
|
64
62
|
logger.info("Successfully loaded skill: %s", name)
|
|
65
63
|
loaded_skills.append(skill)
|
|
@@ -69,7 +67,7 @@ async def get_skills(
|
|
|
69
67
|
return loaded_skills
|
|
70
68
|
|
|
71
69
|
|
|
72
|
-
def get_dune_skill(name: str
|
|
70
|
+
def get_dune_skill(name: str) -> DuneBaseTool | None:
|
|
73
71
|
"""Retrieve a Dune Analytics skill instance by name.
|
|
74
72
|
|
|
75
73
|
Args:
|
|
@@ -87,11 +85,11 @@ def get_dune_skill(name: str, store: SkillStoreABC) -> Optional[DuneBaseTool]:
|
|
|
87
85
|
if name == "fetch_nation_metrics":
|
|
88
86
|
from .fetch_nation_metrics import FetchNationMetrics
|
|
89
87
|
|
|
90
|
-
_skill_cache[name] = FetchNationMetrics(
|
|
88
|
+
_skill_cache[name] = FetchNationMetrics()
|
|
91
89
|
elif name == "fetch_kol_buys":
|
|
92
90
|
from .fetch_kol_buys import FetchKOLBuys
|
|
93
91
|
|
|
94
|
-
_skill_cache[name] = FetchKOLBuys(
|
|
92
|
+
_skill_cache[name] = FetchKOLBuys()
|
|
95
93
|
else:
|
|
96
94
|
logger.warning("Unknown Dune Analytics skill: %s", name)
|
|
97
95
|
return None
|
|
@@ -1,52 +1,48 @@
|
|
|
1
|
-
"""Base module for Dune Analytics skills.
|
|
2
|
-
|
|
3
|
-
Provides shared functionality for interacting with the Dune Analytics API.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
""
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@property
|
|
50
|
-
def category(self) -> str:
|
|
51
|
-
"""Category of the skill."""
|
|
52
|
-
return "dune_analytics"
|
|
1
|
+
"""Base module for Dune Analytics skills.
|
|
2
|
+
|
|
3
|
+
Provides shared functionality for interacting with the Dune Analytics API.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from langchain_core.tools.base import ToolException
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from intentkit.skills.base import IntentKitSkill
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DuneBaseTool(IntentKitSkill):
|
|
13
|
+
"""Base class for Dune Analytics skills.
|
|
14
|
+
|
|
15
|
+
Offers common functionality like API key retrieval and Dune API interaction.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
name: str = Field(description="Tool name")
|
|
19
|
+
description: str = Field(description="Tool description")
|
|
20
|
+
args_schema: type[BaseModel]
|
|
21
|
+
|
|
22
|
+
def get_api_key(self) -> str:
|
|
23
|
+
"""Retrieve the Dune Analytics API key from context.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
API key string.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ToolException: If the API key is not found.
|
|
30
|
+
"""
|
|
31
|
+
context = self.get_context()
|
|
32
|
+
skill_config = context.agent.skill_config(self.category)
|
|
33
|
+
api_key_provider = skill_config.get("api_key_provider")
|
|
34
|
+
if api_key_provider == "agent_owner":
|
|
35
|
+
api_key = skill_config.get("api_key")
|
|
36
|
+
if api_key:
|
|
37
|
+
return api_key
|
|
38
|
+
else:
|
|
39
|
+
raise ToolException("No api_key found in agent_owner configuration")
|
|
40
|
+
else:
|
|
41
|
+
raise ToolException(
|
|
42
|
+
f"Invalid API key provider: {api_key_provider}. Only 'agent_owner' is supported for Dune Analytics."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def category(self) -> str:
|
|
47
|
+
"""Category of the skill."""
|
|
48
|
+
return "dune_analytics"
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
Uses query ID 4832844 to retrieve a list of KOL buy transactions.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
import httpx
|
|
9
9
|
from pydantic import BaseModel, Field
|
|
10
10
|
from tenacity import retry, stop_after_attempt, wait_exponential
|
|
11
11
|
|
|
12
|
-
from intentkit.abstracts.skill import SkillStoreABC
|
|
13
12
|
from intentkit.skills.dune_analytics.base import DuneBaseTool
|
|
14
13
|
|
|
15
14
|
BASE_URL = "https://api.dune.com/api/v1/query"
|
|
@@ -29,7 +28,7 @@ class KOLBuysInput(BaseModel):
|
|
|
29
28
|
class KOLBuyData(BaseModel):
|
|
30
29
|
"""Data model for KOL buy results."""
|
|
31
30
|
|
|
32
|
-
data:
|
|
31
|
+
data: dict[str, Any] = Field(description="KOL buy data from Dune API")
|
|
33
32
|
error: str = Field(default="", description="Error message if fetch failed")
|
|
34
33
|
|
|
35
34
|
|
|
@@ -48,15 +47,14 @@ class FetchKOLBuys(DuneBaseTool):
|
|
|
48
47
|
"Fetches a list of KOL memecoin buy transactions on Solana from Dune Analytics API using query ID 4832844. "
|
|
49
48
|
"Supports a configurable limit for the number of results. Handles rate limits with retries."
|
|
50
49
|
)
|
|
51
|
-
args_schema:
|
|
52
|
-
skill_store: SkillStoreABC = Field(description="Skill store for data persistence")
|
|
50
|
+
args_schema: type[BaseModel] = KOLBuysInput
|
|
53
51
|
|
|
54
52
|
@retry(
|
|
55
53
|
stop=stop_after_attempt(3), wait=wait_exponential(multiplier=5, min=5, max=60)
|
|
56
54
|
)
|
|
57
55
|
async def fetch_data(
|
|
58
56
|
self, query_id: int, api_key: str, limit: int = 10
|
|
59
|
-
) ->
|
|
57
|
+
) -> dict[str, Any]:
|
|
60
58
|
"""Fetch data for a specific Dune query.
|
|
61
59
|
|
|
62
60
|
Args:
|
|
@@ -70,7 +68,7 @@ class FetchKOLBuys(DuneBaseTool):
|
|
|
70
68
|
Raises:
|
|
71
69
|
ToolException: If the API request fails.
|
|
72
70
|
"""
|
|
73
|
-
from
|
|
71
|
+
from langchain_core.tools.base import ToolException
|
|
74
72
|
|
|
75
73
|
url = f"{BASE_URL}/{query_id}/results?limit={limit}"
|
|
76
74
|
headers = {"X-Dune-API-Key": api_key}
|
|
@@ -5,13 +5,12 @@ Supports predefined metrics (e.g., total_users, unique_ai_citizens) or direct qu
|
|
|
5
5
|
|
|
6
6
|
import difflib
|
|
7
7
|
import re
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
11
|
from pydantic import BaseModel, Field
|
|
12
12
|
from tenacity import retry, stop_after_attempt, wait_exponential
|
|
13
13
|
|
|
14
|
-
from intentkit.abstracts.skill import SkillStoreABC
|
|
15
14
|
from intentkit.skills.dune_analytics.base import DuneBaseTool
|
|
16
15
|
|
|
17
16
|
SUPPORTED_QUERIES = {
|
|
@@ -63,14 +62,14 @@ class MetricData(BaseModel):
|
|
|
63
62
|
"""Data model for a single metric result."""
|
|
64
63
|
|
|
65
64
|
metric: str = Field(description="Metric name or query ID")
|
|
66
|
-
data:
|
|
65
|
+
data: dict[str, Any] = Field(description="Metric data from Dune API")
|
|
67
66
|
error: str = Field(default="", description="Error message if fetch failed")
|
|
68
67
|
|
|
69
68
|
|
|
70
69
|
class NationMetricsOutput(BaseModel):
|
|
71
70
|
"""Output schema for Crestal Nation metrics."""
|
|
72
71
|
|
|
73
|
-
metrics:
|
|
72
|
+
metrics: dict[str, MetricData] = Field(
|
|
74
73
|
description="Dictionary of metric names or query IDs to their data"
|
|
75
74
|
)
|
|
76
75
|
summary: str = Field(description="Summary of fetched metrics")
|
|
@@ -85,8 +84,7 @@ class FetchNationMetrics(DuneBaseTool):
|
|
|
85
84
|
"Supports predefined metrics, direct query IDs, or all configured metrics if none specified. "
|
|
86
85
|
"Handles rate limits with retries."
|
|
87
86
|
)
|
|
88
|
-
args_schema:
|
|
89
|
-
skill_store: SkillStoreABC = Field(description="Skill store for data persistence")
|
|
87
|
+
args_schema: type[BaseModel] = NationMetricsInput
|
|
90
88
|
|
|
91
89
|
def normalize_metric(self, metric: str) -> str:
|
|
92
90
|
"""Normalize a metric string for matching.
|
|
@@ -124,7 +122,7 @@ class FetchNationMetrics(DuneBaseTool):
|
|
|
124
122
|
)
|
|
125
123
|
async def fetch_data(
|
|
126
124
|
self, query_id: int, api_key: str, limit: int = 1000
|
|
127
|
-
) ->
|
|
125
|
+
) -> dict[str, Any]:
|
|
128
126
|
"""Fetch data for a specific Dune query.
|
|
129
127
|
|
|
130
128
|
Args:
|
|
@@ -138,7 +136,7 @@ class FetchNationMetrics(DuneBaseTool):
|
|
|
138
136
|
Raises:
|
|
139
137
|
ToolException: If the API request fails.
|
|
140
138
|
"""
|
|
141
|
-
from
|
|
139
|
+
from langchain_core.tools.base import ToolException
|
|
142
140
|
|
|
143
141
|
url = f"{BASE_URL}/{query_id}/results?limit={limit}"
|
|
144
142
|
headers = {"X-Dune-API-Key": api_key}
|