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,472 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Any, Dict, List, Optional, Type
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from langchain_core.runnables import RunnableConfig
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
from web3 import Web3
|
|
8
|
+
|
|
9
|
+
from intentkit.abstracts.skill import SkillStoreABC
|
|
10
|
+
from intentkit.clients import get_cdp_client
|
|
11
|
+
from intentkit.skills.lifi.base import LiFiBaseTool
|
|
12
|
+
from intentkit.skills.lifi.token_quote import TokenQuote
|
|
13
|
+
from intentkit.skills.lifi.utils import (
|
|
14
|
+
ERC20_ABI,
|
|
15
|
+
LIFI_API_URL,
|
|
16
|
+
build_quote_params,
|
|
17
|
+
convert_chain_to_id,
|
|
18
|
+
create_erc20_approve_data,
|
|
19
|
+
format_amount,
|
|
20
|
+
format_transaction_result,
|
|
21
|
+
handle_api_response,
|
|
22
|
+
is_native_token,
|
|
23
|
+
prepare_transaction_params,
|
|
24
|
+
validate_inputs,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TokenExecuteInput(BaseModel):
|
|
29
|
+
"""Input for the TokenExecute skill."""
|
|
30
|
+
|
|
31
|
+
from_chain: str = Field(
|
|
32
|
+
description="The source chain (e.g., 'ETH', 'POL', 'ARB', 'DAI'). Can be chain ID or chain key."
|
|
33
|
+
)
|
|
34
|
+
to_chain: str = Field(
|
|
35
|
+
description="The destination chain (e.g., 'ETH', 'POL', 'ARB', 'DAI'). Can be chain ID or chain key."
|
|
36
|
+
)
|
|
37
|
+
from_token: str = Field(
|
|
38
|
+
description="The token to send (e.g., 'USDC', 'ETH', 'DAI'). Can be token address or symbol."
|
|
39
|
+
)
|
|
40
|
+
to_token: str = Field(
|
|
41
|
+
description="The token to receive (e.g., 'USDC', 'ETH', 'DAI'). Can be token address or symbol."
|
|
42
|
+
)
|
|
43
|
+
from_amount: str = Field(
|
|
44
|
+
description="The amount to send, including all decimals (e.g., '1000000' for 1 USDC with 6 decimals)."
|
|
45
|
+
)
|
|
46
|
+
slippage: float = Field(
|
|
47
|
+
default=0.03,
|
|
48
|
+
description="Maximum acceptable slippage as a decimal (e.g., 0.03 for 3%). Default is 3%.",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TokenExecute(LiFiBaseTool):
|
|
53
|
+
"""Tool for executing token transfers across chains using LiFi.
|
|
54
|
+
|
|
55
|
+
This tool executes actual token transfers and swaps using the CDP wallet provider.
|
|
56
|
+
Requires a properly configured CDP wallet to work.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
name: str = "lifi_token_execute"
|
|
60
|
+
description: str = (
|
|
61
|
+
"Execute a token transfer across blockchains or swap tokens on the same chain.\n"
|
|
62
|
+
"This requires a CDP wallet with sufficient funds and proper network configuration.\n"
|
|
63
|
+
"Use token_quote first to check rates and fees before executing.\n"
|
|
64
|
+
"Supports all major chains like Ethereum, Polygon, Arbitrum, Optimism, Base, and more."
|
|
65
|
+
)
|
|
66
|
+
args_schema: Type[BaseModel] = TokenExecuteInput
|
|
67
|
+
api_url: str = LIFI_API_URL
|
|
68
|
+
|
|
69
|
+
# Configuration options
|
|
70
|
+
default_slippage: float = 0.03
|
|
71
|
+
allowed_chains: Optional[List[str]] = None
|
|
72
|
+
max_execution_time: int = 300
|
|
73
|
+
quote_tool: TokenQuote = Field(default=None, exclude=True)
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
skill_store: SkillStoreABC,
|
|
78
|
+
default_slippage: float = 0.03,
|
|
79
|
+
allowed_chains: Optional[List[str]] = None,
|
|
80
|
+
max_execution_time: int = 300,
|
|
81
|
+
):
|
|
82
|
+
"""Initialize the TokenExecute skill with configuration options."""
|
|
83
|
+
super().__init__(skill_store=skill_store)
|
|
84
|
+
self.default_slippage = default_slippage
|
|
85
|
+
self.allowed_chains = allowed_chains
|
|
86
|
+
self.max_execution_time = max_execution_time
|
|
87
|
+
# Initialize quote tool if not set
|
|
88
|
+
if not self.quote_tool:
|
|
89
|
+
self.quote_tool = TokenQuote(
|
|
90
|
+
skill_store=skill_store,
|
|
91
|
+
default_slippage=default_slippage,
|
|
92
|
+
allowed_chains=allowed_chains,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def _format_quote_result(self, data: Dict[str, Any]) -> str:
|
|
96
|
+
"""Format the quote result in a readable format."""
|
|
97
|
+
# Use the same formatting as token_quote
|
|
98
|
+
return self.quote_tool._format_quote_result(data)
|
|
99
|
+
|
|
100
|
+
async def _arun(
|
|
101
|
+
self,
|
|
102
|
+
config: RunnableConfig,
|
|
103
|
+
from_chain: str,
|
|
104
|
+
to_chain: str,
|
|
105
|
+
from_token: str,
|
|
106
|
+
to_token: str,
|
|
107
|
+
from_amount: str,
|
|
108
|
+
slippage: float = None,
|
|
109
|
+
**kwargs,
|
|
110
|
+
) -> str:
|
|
111
|
+
"""Execute a token transfer."""
|
|
112
|
+
try:
|
|
113
|
+
# Use provided slippage or default
|
|
114
|
+
if slippage is None:
|
|
115
|
+
slippage = self.default_slippage
|
|
116
|
+
|
|
117
|
+
# Validate all inputs
|
|
118
|
+
validation_error = validate_inputs(
|
|
119
|
+
from_chain,
|
|
120
|
+
to_chain,
|
|
121
|
+
from_token,
|
|
122
|
+
to_token,
|
|
123
|
+
from_amount,
|
|
124
|
+
slippage,
|
|
125
|
+
self.allowed_chains,
|
|
126
|
+
)
|
|
127
|
+
if validation_error:
|
|
128
|
+
return validation_error
|
|
129
|
+
|
|
130
|
+
# Get agent context for CDP wallet
|
|
131
|
+
context = self.context_from_config(config)
|
|
132
|
+
agent_id = context.agent.id
|
|
133
|
+
|
|
134
|
+
self.logger.info(
|
|
135
|
+
f"Executing LiFi transfer: {from_amount} {from_token} on {from_chain} -> {to_token} on {to_chain}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Get CDP wallet provider
|
|
139
|
+
cdp_wallet_provider = await self._get_cdp_wallet_provider(agent_id)
|
|
140
|
+
if isinstance(cdp_wallet_provider, str): # Error message
|
|
141
|
+
return cdp_wallet_provider
|
|
142
|
+
|
|
143
|
+
# Get wallet address
|
|
144
|
+
from_address = cdp_wallet_provider.get_address()
|
|
145
|
+
if not from_address:
|
|
146
|
+
return "No wallet address available. Please check your CDP wallet configuration."
|
|
147
|
+
|
|
148
|
+
# Get quote and execute transfer
|
|
149
|
+
async with httpx.AsyncClient() as client:
|
|
150
|
+
# Step 1: Get quote
|
|
151
|
+
quote_data = await self._get_quote(
|
|
152
|
+
client,
|
|
153
|
+
from_chain,
|
|
154
|
+
to_chain,
|
|
155
|
+
from_token,
|
|
156
|
+
to_token,
|
|
157
|
+
from_amount,
|
|
158
|
+
slippage,
|
|
159
|
+
from_address,
|
|
160
|
+
)
|
|
161
|
+
if isinstance(quote_data, str): # Error message
|
|
162
|
+
return quote_data
|
|
163
|
+
|
|
164
|
+
# Step 2: Handle token approval if needed
|
|
165
|
+
approval_result = await self._handle_token_approval(
|
|
166
|
+
cdp_wallet_provider, quote_data
|
|
167
|
+
)
|
|
168
|
+
if approval_result:
|
|
169
|
+
self.logger.info(f"Token approval completed: {approval_result}")
|
|
170
|
+
|
|
171
|
+
# Step 3: Execute transaction
|
|
172
|
+
tx_hash = await self._execute_transfer_transaction(
|
|
173
|
+
cdp_wallet_provider, quote_data
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Step 4: Monitor status and return result
|
|
177
|
+
return await self._finalize_transfer(
|
|
178
|
+
client, tx_hash, from_chain, to_chain, quote_data
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
self.logger.error("LiFi_Error: %s", str(e))
|
|
183
|
+
return f"An unexpected error occurred: {str(e)}"
|
|
184
|
+
|
|
185
|
+
async def _get_cdp_wallet_provider(self, agent_id: str):
|
|
186
|
+
"""Get CDP wallet provider with error handling."""
|
|
187
|
+
try:
|
|
188
|
+
cdp_client = await get_cdp_client(agent_id, self.skill_store)
|
|
189
|
+
if not cdp_client:
|
|
190
|
+
return "CDP client not available. Please ensure your agent has CDP wallet configuration."
|
|
191
|
+
|
|
192
|
+
cdp_wallet_provider = await cdp_client.get_wallet_provider()
|
|
193
|
+
if not cdp_wallet_provider:
|
|
194
|
+
return "CDP wallet provider not configured. Please set up your agent's CDP wallet first."
|
|
195
|
+
|
|
196
|
+
return cdp_wallet_provider
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
self.logger.error("LiFi_CDP_Error: %s", str(e))
|
|
200
|
+
return f"Cannot access CDP wallet: {str(e)}\n\nPlease ensure your agent has a properly configured CDP wallet with sufficient funds."
|
|
201
|
+
|
|
202
|
+
async def _get_quote(
|
|
203
|
+
self,
|
|
204
|
+
client: httpx.AsyncClient,
|
|
205
|
+
from_chain: str,
|
|
206
|
+
to_chain: str,
|
|
207
|
+
from_token: str,
|
|
208
|
+
to_token: str,
|
|
209
|
+
from_amount: str,
|
|
210
|
+
slippage: float,
|
|
211
|
+
from_address: str,
|
|
212
|
+
) -> Dict[str, Any]:
|
|
213
|
+
"""Get quote from LiFi API."""
|
|
214
|
+
api_params = build_quote_params(
|
|
215
|
+
from_chain,
|
|
216
|
+
to_chain,
|
|
217
|
+
from_token,
|
|
218
|
+
to_token,
|
|
219
|
+
from_amount,
|
|
220
|
+
slippage,
|
|
221
|
+
from_address,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
response = await client.get(
|
|
226
|
+
f"{self.api_url}/quote",
|
|
227
|
+
params=api_params,
|
|
228
|
+
timeout=30.0,
|
|
229
|
+
)
|
|
230
|
+
except httpx.TimeoutException:
|
|
231
|
+
return "Request timed out. The LiFi service might be temporarily unavailable. Please try again."
|
|
232
|
+
except httpx.ConnectError:
|
|
233
|
+
return "Connection error. Unable to reach LiFi service. Please check your internet connection."
|
|
234
|
+
except Exception as e:
|
|
235
|
+
self.logger.error("LiFi_API_Error: %s", str(e))
|
|
236
|
+
return f"Error making API request: {str(e)}"
|
|
237
|
+
|
|
238
|
+
# Handle response
|
|
239
|
+
data, error = handle_api_response(
|
|
240
|
+
response, from_token, from_chain, to_token, to_chain
|
|
241
|
+
)
|
|
242
|
+
if error:
|
|
243
|
+
self.logger.error("LiFi_API_Error: %s", error)
|
|
244
|
+
return error
|
|
245
|
+
|
|
246
|
+
# Validate transaction request
|
|
247
|
+
transaction_request = data.get("transactionRequest")
|
|
248
|
+
if not transaction_request:
|
|
249
|
+
return "No transaction request found in the quote. Cannot execute transfer."
|
|
250
|
+
|
|
251
|
+
return data
|
|
252
|
+
|
|
253
|
+
async def _handle_token_approval(
|
|
254
|
+
self, wallet_provider, quote_data: Dict[str, Any]
|
|
255
|
+
) -> Optional[str]:
|
|
256
|
+
"""Handle ERC20 token approval if needed."""
|
|
257
|
+
estimate = quote_data.get("estimate", {})
|
|
258
|
+
approval_address = estimate.get("approvalAddress")
|
|
259
|
+
from_token_info = quote_data.get("action", {}).get("fromToken", {})
|
|
260
|
+
from_token_address = from_token_info.get("address", "")
|
|
261
|
+
from_amount = quote_data.get("action", {}).get("fromAmount", "0")
|
|
262
|
+
|
|
263
|
+
# Skip approval for native tokens
|
|
264
|
+
if is_native_token(from_token_address) or not approval_address:
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
self.logger.info("Checking token approval for ERC20 transfer...")
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
return await self._check_and_set_allowance(
|
|
271
|
+
wallet_provider, from_token_address, approval_address, from_amount
|
|
272
|
+
)
|
|
273
|
+
except Exception as e:
|
|
274
|
+
self.logger.error("LiFi_Token_Approval_Error: %s", str(e))
|
|
275
|
+
raise Exception(f"Failed to approve token: {str(e)}")
|
|
276
|
+
|
|
277
|
+
async def _execute_transfer_transaction(
|
|
278
|
+
self, wallet_provider, quote_data: Dict[str, Any]
|
|
279
|
+
) -> str:
|
|
280
|
+
"""Execute the main transfer transaction."""
|
|
281
|
+
transaction_request = quote_data.get("transactionRequest")
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
tx_params = prepare_transaction_params(transaction_request)
|
|
285
|
+
self.logger.info(
|
|
286
|
+
f"Sending transaction to {tx_params['to']} with value {tx_params['value']}"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Send transaction
|
|
290
|
+
tx_hash = wallet_provider.send_transaction(tx_params)
|
|
291
|
+
|
|
292
|
+
# Wait for confirmation
|
|
293
|
+
receipt = wallet_provider.wait_for_transaction_receipt(tx_hash)
|
|
294
|
+
if not receipt or receipt.get("status") == 0:
|
|
295
|
+
raise Exception(f"Transaction failed: {tx_hash}")
|
|
296
|
+
|
|
297
|
+
return tx_hash
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
self.logger.error("LiFi_Execution_Error: %s", str(e))
|
|
301
|
+
raise Exception(f"Failed to execute transaction: {str(e)}")
|
|
302
|
+
|
|
303
|
+
async def _finalize_transfer(
|
|
304
|
+
self,
|
|
305
|
+
client: httpx.AsyncClient,
|
|
306
|
+
tx_hash: str,
|
|
307
|
+
from_chain: str,
|
|
308
|
+
to_chain: str,
|
|
309
|
+
quote_data: Dict[str, Any],
|
|
310
|
+
) -> str:
|
|
311
|
+
"""Finalize transfer and return formatted result."""
|
|
312
|
+
self.logger.info(f"Transaction sent: {tx_hash}")
|
|
313
|
+
|
|
314
|
+
# Get chain ID for explorer URL
|
|
315
|
+
from_chain_id = convert_chain_to_id(from_chain)
|
|
316
|
+
|
|
317
|
+
# Extract token info for result formatting
|
|
318
|
+
action = quote_data.get("action", {})
|
|
319
|
+
from_token_info = action.get("fromToken", {})
|
|
320
|
+
to_token_info = action.get("toToken", {})
|
|
321
|
+
|
|
322
|
+
token_info = {
|
|
323
|
+
"symbol": f"{from_token_info.get('symbol', 'Unknown')} → {to_token_info.get('symbol', 'Unknown')}",
|
|
324
|
+
"amount": format_amount(
|
|
325
|
+
action.get("fromAmount", "0"), from_token_info.get("decimals", 18)
|
|
326
|
+
),
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
# Format transaction result with explorer URL
|
|
330
|
+
transaction_result = format_transaction_result(
|
|
331
|
+
tx_hash, from_chain_id, token_info
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Format quote details
|
|
335
|
+
formatted_quote = self._format_quote_result(quote_data)
|
|
336
|
+
|
|
337
|
+
# Handle cross-chain vs same-chain transfers
|
|
338
|
+
if from_chain.lower() != to_chain.lower():
|
|
339
|
+
self.logger.info("Monitoring cross-chain transfer status...")
|
|
340
|
+
status_result = await self._monitor_transfer_status(
|
|
341
|
+
client, tx_hash, from_chain, to_chain
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
return f"""**Token Transfer Executed Successfully**
|
|
345
|
+
|
|
346
|
+
{transaction_result}
|
|
347
|
+
{status_result}
|
|
348
|
+
|
|
349
|
+
{formatted_quote}
|
|
350
|
+
"""
|
|
351
|
+
else:
|
|
352
|
+
return f"""**Token Swap Executed Successfully**
|
|
353
|
+
|
|
354
|
+
{transaction_result}
|
|
355
|
+
**Status:** Completed (same-chain swap)
|
|
356
|
+
|
|
357
|
+
{formatted_quote}
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
async def _monitor_transfer_status(
|
|
361
|
+
self, client: httpx.AsyncClient, tx_hash: str, from_chain: str, to_chain: str
|
|
362
|
+
) -> str:
|
|
363
|
+
"""Monitor the status of a cross-chain transfer."""
|
|
364
|
+
max_attempts = min(self.max_execution_time // 10, 30) # Check every 10 seconds
|
|
365
|
+
attempt = 0
|
|
366
|
+
|
|
367
|
+
while attempt < max_attempts:
|
|
368
|
+
try:
|
|
369
|
+
status_response = await client.get(
|
|
370
|
+
f"{self.api_url}/status",
|
|
371
|
+
params={
|
|
372
|
+
"txHash": tx_hash,
|
|
373
|
+
"fromChain": from_chain,
|
|
374
|
+
"toChain": to_chain,
|
|
375
|
+
},
|
|
376
|
+
timeout=10.0,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if status_response.status_code == 200:
|
|
380
|
+
status_data = status_response.json()
|
|
381
|
+
status = status_data.get("status", "UNKNOWN")
|
|
382
|
+
|
|
383
|
+
if status == "DONE":
|
|
384
|
+
receiving_tx = status_data.get("receiving", {}).get("txHash")
|
|
385
|
+
if receiving_tx:
|
|
386
|
+
return (
|
|
387
|
+
f"**Status:** Complete (destination tx: {receiving_tx})"
|
|
388
|
+
)
|
|
389
|
+
else:
|
|
390
|
+
return "**Status:** Complete"
|
|
391
|
+
elif status == "FAILED":
|
|
392
|
+
return "**Status:** Failed"
|
|
393
|
+
elif status in ["PENDING", "NOT_FOUND"]:
|
|
394
|
+
# Continue monitoring
|
|
395
|
+
pass
|
|
396
|
+
else:
|
|
397
|
+
return f"**Status:** {status}"
|
|
398
|
+
|
|
399
|
+
except Exception as e:
|
|
400
|
+
self.logger.warning(
|
|
401
|
+
f"Status check failed (attempt {attempt + 1}): {str(e)}"
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
attempt += 1
|
|
405
|
+
if attempt < max_attempts:
|
|
406
|
+
await asyncio.sleep(10) # Wait 10 seconds before next check
|
|
407
|
+
|
|
408
|
+
return "**Status:** Processing (monitoring timed out, but transfer may still complete)"
|
|
409
|
+
|
|
410
|
+
async def _check_and_set_allowance(
|
|
411
|
+
self,
|
|
412
|
+
wallet_provider,
|
|
413
|
+
token_address: str,
|
|
414
|
+
approval_address: str,
|
|
415
|
+
amount: str,
|
|
416
|
+
) -> Optional[str]:
|
|
417
|
+
"""Check if token allowance is sufficient and set approval if needed."""
|
|
418
|
+
try:
|
|
419
|
+
# Normalize addresses
|
|
420
|
+
token_address = Web3.to_checksum_address(token_address)
|
|
421
|
+
approval_address = Web3.to_checksum_address(approval_address)
|
|
422
|
+
wallet_address = wallet_provider.get_address()
|
|
423
|
+
|
|
424
|
+
# Check current allowance
|
|
425
|
+
try:
|
|
426
|
+
current_allowance = wallet_provider.read_contract(
|
|
427
|
+
contract_address=token_address,
|
|
428
|
+
abi=ERC20_ABI,
|
|
429
|
+
function_name="allowance",
|
|
430
|
+
args=[wallet_address, approval_address],
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
required_amount = int(amount)
|
|
434
|
+
|
|
435
|
+
if current_allowance >= required_amount:
|
|
436
|
+
self.logger.info(
|
|
437
|
+
f"Sufficient allowance already exists: {current_allowance}"
|
|
438
|
+
)
|
|
439
|
+
return None # No approval needed
|
|
440
|
+
|
|
441
|
+
except Exception as e:
|
|
442
|
+
self.logger.warning(f"Could not check current allowance: {str(e)}")
|
|
443
|
+
# Continue with approval anyway
|
|
444
|
+
|
|
445
|
+
# Set approval for the required amount
|
|
446
|
+
self.logger.info(
|
|
447
|
+
f"Setting token approval for {amount} tokens to {approval_address}"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# Create approval transaction
|
|
451
|
+
approve_data = create_erc20_approve_data(approval_address, amount)
|
|
452
|
+
|
|
453
|
+
# Send approval transaction
|
|
454
|
+
approval_tx_hash = wallet_provider.send_transaction(
|
|
455
|
+
{
|
|
456
|
+
"to": token_address,
|
|
457
|
+
"data": approve_data,
|
|
458
|
+
"value": 0,
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Wait for approval transaction confirmation
|
|
463
|
+
receipt = wallet_provider.wait_for_transaction_receipt(approval_tx_hash)
|
|
464
|
+
|
|
465
|
+
if not receipt or receipt.get("status") == 0:
|
|
466
|
+
raise Exception(f"Approval transaction failed: {approval_tx_hash}")
|
|
467
|
+
|
|
468
|
+
return approval_tx_hash
|
|
469
|
+
|
|
470
|
+
except Exception as e:
|
|
471
|
+
self.logger.error(f"Token approval failed: {str(e)}")
|
|
472
|
+
raise Exception(f"Failed to approve token transfer: {str(e)}")
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Type
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from langchain_core.runnables import RunnableConfig
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from intentkit.abstracts.skill import SkillStoreABC
|
|
8
|
+
from intentkit.skills.lifi.base import LiFiBaseTool
|
|
9
|
+
from intentkit.skills.lifi.utils import (
|
|
10
|
+
LIFI_API_URL,
|
|
11
|
+
build_quote_params,
|
|
12
|
+
format_duration,
|
|
13
|
+
format_fees_and_gas,
|
|
14
|
+
format_quote_basic_info,
|
|
15
|
+
format_route_info,
|
|
16
|
+
handle_api_response,
|
|
17
|
+
validate_inputs,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TokenQuoteInput(BaseModel):
|
|
22
|
+
"""Input for the TokenQuote skill."""
|
|
23
|
+
|
|
24
|
+
from_chain: str = Field(
|
|
25
|
+
description="The source chain (e.g., 'ETH', 'POL', 'ARB', 'DAI'). Can be chain ID or chain key."
|
|
26
|
+
)
|
|
27
|
+
to_chain: str = Field(
|
|
28
|
+
description="The destination chain (e.g., 'ETH', 'POL', 'ARB', 'DAI'). Can be chain ID or chain key."
|
|
29
|
+
)
|
|
30
|
+
from_token: str = Field(
|
|
31
|
+
description="The token to send (e.g., 'USDC', 'ETH', 'DAI'). Can be token address or symbol."
|
|
32
|
+
)
|
|
33
|
+
to_token: str = Field(
|
|
34
|
+
description="The token to receive (e.g., 'USDC', 'ETH', 'DAI'). Can be token address or symbol."
|
|
35
|
+
)
|
|
36
|
+
from_amount: str = Field(
|
|
37
|
+
description="The amount to send, including all decimals (e.g., '1000000' for 1 USDC with 6 decimals)."
|
|
38
|
+
)
|
|
39
|
+
slippage: float = Field(
|
|
40
|
+
default=0.03,
|
|
41
|
+
description="The maximum allowed slippage for the transaction (0.03 represents 3%).",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TokenQuote(LiFiBaseTool):
|
|
46
|
+
"""Tool for getting token transfer quotes across chains using LiFi.
|
|
47
|
+
|
|
48
|
+
This tool provides quotes for token transfers and swaps without executing transactions.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
name: str = "lifi_token_quote"
|
|
52
|
+
description: str = (
|
|
53
|
+
"Get a quote for transferring tokens across blockchains or swapping tokens.\n"
|
|
54
|
+
"Use this tool to check rates, fees, and estimated time for token transfers without executing them.\n"
|
|
55
|
+
"Supports all major chains like Ethereum, Polygon, Arbitrum, Optimism, Base, and more."
|
|
56
|
+
)
|
|
57
|
+
args_schema: Type[BaseModel] = TokenQuoteInput
|
|
58
|
+
api_url: str = LIFI_API_URL
|
|
59
|
+
|
|
60
|
+
# Configuration options
|
|
61
|
+
default_slippage: float = 0.03
|
|
62
|
+
allowed_chains: Optional[List[str]] = None
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
skill_store: SkillStoreABC,
|
|
67
|
+
default_slippage: float = 0.03,
|
|
68
|
+
allowed_chains: Optional[List[str]] = None,
|
|
69
|
+
):
|
|
70
|
+
"""Initialize the TokenQuote skill with configuration options."""
|
|
71
|
+
super().__init__(skill_store=skill_store)
|
|
72
|
+
self.default_slippage = default_slippage
|
|
73
|
+
self.allowed_chains = allowed_chains
|
|
74
|
+
|
|
75
|
+
async def _arun(
|
|
76
|
+
self,
|
|
77
|
+
config: RunnableConfig,
|
|
78
|
+
from_chain: str,
|
|
79
|
+
to_chain: str,
|
|
80
|
+
from_token: str,
|
|
81
|
+
to_token: str,
|
|
82
|
+
from_amount: str,
|
|
83
|
+
slippage: float = None,
|
|
84
|
+
**kwargs,
|
|
85
|
+
) -> str:
|
|
86
|
+
"""Get a quote for token transfer."""
|
|
87
|
+
try:
|
|
88
|
+
# Use provided slippage or default
|
|
89
|
+
if slippage is None:
|
|
90
|
+
slippage = self.default_slippage
|
|
91
|
+
|
|
92
|
+
# Validate all inputs
|
|
93
|
+
validation_error = validate_inputs(
|
|
94
|
+
from_chain,
|
|
95
|
+
to_chain,
|
|
96
|
+
from_token,
|
|
97
|
+
to_token,
|
|
98
|
+
from_amount,
|
|
99
|
+
slippage,
|
|
100
|
+
self.allowed_chains,
|
|
101
|
+
)
|
|
102
|
+
if validation_error:
|
|
103
|
+
return validation_error
|
|
104
|
+
|
|
105
|
+
self.logger.info(
|
|
106
|
+
f"Requesting LiFi quote: {from_amount} {from_token} on {from_chain} -> {to_token} on {to_chain}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Build API parameters
|
|
110
|
+
api_params = build_quote_params(
|
|
111
|
+
from_chain, to_chain, from_token, to_token, from_amount, slippage
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Make API request
|
|
115
|
+
async with httpx.AsyncClient() as client:
|
|
116
|
+
try:
|
|
117
|
+
response = await client.get(
|
|
118
|
+
f"{self.api_url}/quote",
|
|
119
|
+
params=api_params,
|
|
120
|
+
timeout=30.0,
|
|
121
|
+
)
|
|
122
|
+
except httpx.TimeoutException:
|
|
123
|
+
return "Request timed out. The LiFi service might be temporarily unavailable. Please try again."
|
|
124
|
+
except httpx.ConnectError:
|
|
125
|
+
return "Connection error. Unable to reach LiFi service. Please check your internet connection."
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self.logger.error("LiFi_API_Error: %s", str(e))
|
|
128
|
+
return f"Error making API request: {str(e)}"
|
|
129
|
+
|
|
130
|
+
# Handle response
|
|
131
|
+
data, error = handle_api_response(
|
|
132
|
+
response, from_token, from_chain, to_token, to_chain
|
|
133
|
+
)
|
|
134
|
+
if error:
|
|
135
|
+
self.logger.error("LiFi_API_Error: %s", error)
|
|
136
|
+
return error
|
|
137
|
+
|
|
138
|
+
# Format the quote result
|
|
139
|
+
return self._format_quote_result(data)
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
self.logger.error("LiFi_Error: %s", str(e))
|
|
143
|
+
return f"An unexpected error occurred: {str(e)}"
|
|
144
|
+
|
|
145
|
+
def _format_quote_result(self, data: Dict[str, Any]) -> str:
|
|
146
|
+
"""Format quote result into human-readable text."""
|
|
147
|
+
try:
|
|
148
|
+
# Get basic info
|
|
149
|
+
info = format_quote_basic_info(data)
|
|
150
|
+
|
|
151
|
+
# Build result string
|
|
152
|
+
result = "### Token Transfer Quote\n\n"
|
|
153
|
+
result += f"**From:** {info['from_amount']} {info['from_token']} on {info['from_chain']}\n"
|
|
154
|
+
result += f"**To:** {info['to_amount']} {info['to_token']} on {info['to_chain']}\n"
|
|
155
|
+
result += (
|
|
156
|
+
f"**Minimum Received:** {info['to_amount_min']} {info['to_token']}\n"
|
|
157
|
+
)
|
|
158
|
+
result += f"**Bridge/Exchange:** {info['tool']}\n\n"
|
|
159
|
+
|
|
160
|
+
# Add USD values if available
|
|
161
|
+
if info["from_amount_usd"] and info["to_amount_usd"]:
|
|
162
|
+
result += f"**Value:** ${info['from_amount_usd']} → ${info['to_amount_usd']}\n\n"
|
|
163
|
+
|
|
164
|
+
# Add execution time estimate
|
|
165
|
+
if info["execution_duration"]:
|
|
166
|
+
time_str = format_duration(info["execution_duration"])
|
|
167
|
+
result += f"**Estimated Time:** {time_str}\n\n"
|
|
168
|
+
|
|
169
|
+
# Add fees and gas costs
|
|
170
|
+
fees_text, gas_text = format_fees_and_gas(data)
|
|
171
|
+
if fees_text:
|
|
172
|
+
result += fees_text + "\n"
|
|
173
|
+
if gas_text:
|
|
174
|
+
result += gas_text + "\n"
|
|
175
|
+
|
|
176
|
+
# Add route information
|
|
177
|
+
route_text = format_route_info(data)
|
|
178
|
+
if route_text:
|
|
179
|
+
result += route_text + "\n"
|
|
180
|
+
|
|
181
|
+
result += "---\n"
|
|
182
|
+
result += (
|
|
183
|
+
"*Use token_execute to perform this transfer with your CDP wallet*"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
self.logger.error("Format_Error: %s", str(e))
|
|
190
|
+
return f"Quote received but formatting failed: {str(e)}\nRaw data: {str(data)[:500]}..."
|