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,656 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LiFi Skills Utilities
|
|
3
|
+
|
|
4
|
+
Common utilities and helper functions for LiFi token transfer skills.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from web3 import Web3
|
|
11
|
+
|
|
12
|
+
# Constants
|
|
13
|
+
LIFI_API_URL = "https://li.quest/v1"
|
|
14
|
+
DUMMY_ADDRESS = "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0" # For quotes
|
|
15
|
+
|
|
16
|
+
# Chain ID to name mapping (includes mainnet and testnet)
|
|
17
|
+
CHAIN_NAMES = {
|
|
18
|
+
# Mainnet chains
|
|
19
|
+
1: "Ethereum",
|
|
20
|
+
10: "Optimism",
|
|
21
|
+
56: "BNB Chain",
|
|
22
|
+
100: "Gnosis Chain",
|
|
23
|
+
137: "Polygon",
|
|
24
|
+
250: "Fantom",
|
|
25
|
+
8453: "Base",
|
|
26
|
+
42161: "Arbitrum One",
|
|
27
|
+
43114: "Avalanche",
|
|
28
|
+
59144: "Linea",
|
|
29
|
+
324: "zkSync Era",
|
|
30
|
+
1101: "Polygon zkEVM",
|
|
31
|
+
534352: "Scroll",
|
|
32
|
+
# Testnet chains
|
|
33
|
+
11155111: "Ethereum Sepolia",
|
|
34
|
+
84532: "Base Sepolia",
|
|
35
|
+
421614: "Arbitrum Sepolia",
|
|
36
|
+
11155420: "Optimism Sepolia",
|
|
37
|
+
80001: "Polygon Mumbai",
|
|
38
|
+
5: "Ethereum Goerli", # Legacy testnet
|
|
39
|
+
420: "Optimism Goerli", # Legacy testnet
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Standard ERC20 ABI for allowance and approve functions
|
|
43
|
+
ERC20_ABI = [
|
|
44
|
+
{
|
|
45
|
+
"constant": True,
|
|
46
|
+
"inputs": [
|
|
47
|
+
{"name": "_owner", "type": "address"},
|
|
48
|
+
{"name": "_spender", "type": "address"},
|
|
49
|
+
],
|
|
50
|
+
"name": "allowance",
|
|
51
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
52
|
+
"type": "function",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"constant": False,
|
|
56
|
+
"inputs": [
|
|
57
|
+
{"name": "_spender", "type": "address"},
|
|
58
|
+
{"name": "_value", "type": "uint256"},
|
|
59
|
+
],
|
|
60
|
+
"name": "approve",
|
|
61
|
+
"outputs": [{"name": "", "type": "bool"}],
|
|
62
|
+
"type": "function",
|
|
63
|
+
},
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def validate_inputs(
|
|
68
|
+
from_chain: str,
|
|
69
|
+
to_chain: str,
|
|
70
|
+
from_token: str,
|
|
71
|
+
to_token: str,
|
|
72
|
+
from_amount: str,
|
|
73
|
+
slippage: float,
|
|
74
|
+
allowed_chains: Optional[List[str]] = None,
|
|
75
|
+
) -> Optional[str]:
|
|
76
|
+
"""
|
|
77
|
+
Validate all input parameters for LiFi operations.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
None if valid, error message string if invalid
|
|
81
|
+
"""
|
|
82
|
+
# Validate slippage
|
|
83
|
+
if slippage < 0.001 or slippage > 0.5:
|
|
84
|
+
return "Invalid slippage: must be between 0.001 (0.1%) and 0.5 (50%)"
|
|
85
|
+
|
|
86
|
+
# Validate chain identifiers can be converted to chain IDs
|
|
87
|
+
try:
|
|
88
|
+
convert_chain_to_id(from_chain)
|
|
89
|
+
except ValueError as e:
|
|
90
|
+
return f"Invalid source chain: {str(e)}"
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
convert_chain_to_id(to_chain)
|
|
94
|
+
except ValueError as e:
|
|
95
|
+
return f"Invalid destination chain: {str(e)}"
|
|
96
|
+
|
|
97
|
+
# Validate chains if restricted (use original chain names for restriction check)
|
|
98
|
+
if allowed_chains:
|
|
99
|
+
if from_chain not in allowed_chains:
|
|
100
|
+
return f"Source chain '{from_chain}' is not allowed. Allowed chains: {', '.join(allowed_chains)}"
|
|
101
|
+
if to_chain not in allowed_chains:
|
|
102
|
+
return f"Destination chain '{to_chain}' is not allowed. Allowed chains: {', '.join(allowed_chains)}"
|
|
103
|
+
|
|
104
|
+
# Validate amount is numeric and positive
|
|
105
|
+
try:
|
|
106
|
+
amount_float = float(from_amount)
|
|
107
|
+
if amount_float <= 0:
|
|
108
|
+
return "Amount must be greater than 0"
|
|
109
|
+
except ValueError:
|
|
110
|
+
return f"Invalid amount format: {from_amount}. Must be a numeric value."
|
|
111
|
+
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def format_amount(amount: str, decimals: int) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Format amount from wei/smallest unit to human readable.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
amount: Amount in smallest unit (wei/satoshi/etc)
|
|
121
|
+
decimals: Number of decimal places for the token
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Formatted amount string
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
amount_int = int(amount)
|
|
128
|
+
amount_float = amount_int / (10**decimals)
|
|
129
|
+
|
|
130
|
+
# Format with appropriate precision
|
|
131
|
+
if amount_float >= 1000:
|
|
132
|
+
return f"{amount_float:,.2f}"
|
|
133
|
+
elif amount_float >= 1:
|
|
134
|
+
return f"{amount_float:.4f}"
|
|
135
|
+
elif amount_float >= 0.01:
|
|
136
|
+
return f"{amount_float:.6f}"
|
|
137
|
+
else:
|
|
138
|
+
return f"{amount_float:.8f}"
|
|
139
|
+
except (ValueError, TypeError):
|
|
140
|
+
return str(amount)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_chain_name(chain_id: int) -> str:
|
|
144
|
+
"""
|
|
145
|
+
Get human readable chain name from chain ID.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
chain_id: Blockchain chain ID
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Human readable chain name
|
|
152
|
+
"""
|
|
153
|
+
return CHAIN_NAMES.get(chain_id, f"Chain {chain_id}")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def format_duration(duration: int) -> str:
|
|
157
|
+
"""
|
|
158
|
+
Format duration in seconds to human readable format.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
duration: Duration in seconds
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Formatted duration string
|
|
165
|
+
"""
|
|
166
|
+
if duration < 60:
|
|
167
|
+
return f"{duration} seconds"
|
|
168
|
+
elif duration < 3600:
|
|
169
|
+
return f"{duration // 60} minutes {duration % 60} seconds"
|
|
170
|
+
else:
|
|
171
|
+
hours = duration // 3600
|
|
172
|
+
minutes = (duration % 3600) // 60
|
|
173
|
+
return f"{hours} hours {minutes} minutes"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def handle_api_response(
|
|
177
|
+
response: httpx.Response,
|
|
178
|
+
from_token: str,
|
|
179
|
+
from_chain: str,
|
|
180
|
+
to_token: str,
|
|
181
|
+
to_chain: str,
|
|
182
|
+
) -> Tuple[Optional[Dict], Optional[str]]:
|
|
183
|
+
"""
|
|
184
|
+
Handle LiFi API response and return data or error message.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
response: HTTP response from LiFi API
|
|
188
|
+
from_token, from_chain, to_token, to_chain: Transfer parameters for error messages
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Tuple of (data, error_message). One will be None.
|
|
192
|
+
"""
|
|
193
|
+
if response.status_code == 400:
|
|
194
|
+
try:
|
|
195
|
+
error_data = response.json()
|
|
196
|
+
error_message = error_data.get("message", response.text)
|
|
197
|
+
return None, f"Invalid request: {error_message}"
|
|
198
|
+
except (ValueError, TypeError, AttributeError):
|
|
199
|
+
return None, f"Invalid request: {response.text}"
|
|
200
|
+
elif response.status_code == 404:
|
|
201
|
+
return (
|
|
202
|
+
None,
|
|
203
|
+
f"No route found for transfer from {from_token} on {from_chain} to {to_token} on {to_chain}. Try different tokens or chains.",
|
|
204
|
+
)
|
|
205
|
+
elif response.status_code != 200:
|
|
206
|
+
return None, f"LiFi API error ({response.status_code}): {response.text}"
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
data = response.json()
|
|
210
|
+
if not isinstance(data, dict):
|
|
211
|
+
return None, "Invalid response format from LiFi API."
|
|
212
|
+
return data, None
|
|
213
|
+
except Exception:
|
|
214
|
+
return None, "Invalid response from LiFi API. Please try again."
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def convert_chain_to_id(chain: str) -> int:
|
|
218
|
+
"""
|
|
219
|
+
Convert chain identifier to numeric chain ID.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
chain: Chain identifier (can be name, key, or numeric ID as string)
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Numeric chain ID
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
ValueError: If chain identifier is not recognized
|
|
229
|
+
"""
|
|
230
|
+
# If it's already a number, return it
|
|
231
|
+
if chain.isdigit():
|
|
232
|
+
return int(chain)
|
|
233
|
+
|
|
234
|
+
# Chain name/key to ID mapping
|
|
235
|
+
chain_mapping = {
|
|
236
|
+
# Mainnet chains
|
|
237
|
+
"ethereum": 1,
|
|
238
|
+
"eth": 1,
|
|
239
|
+
"1": 1,
|
|
240
|
+
"optimism": 10,
|
|
241
|
+
"opt": 10,
|
|
242
|
+
"10": 10,
|
|
243
|
+
"binance": 56,
|
|
244
|
+
"bsc": 56,
|
|
245
|
+
"bnb": 56,
|
|
246
|
+
"56": 56,
|
|
247
|
+
"gnosis": 100,
|
|
248
|
+
"100": 100,
|
|
249
|
+
"polygon": 137,
|
|
250
|
+
"pol": 137,
|
|
251
|
+
"matic": 137,
|
|
252
|
+
"137": 137,
|
|
253
|
+
"fantom": 250,
|
|
254
|
+
"ftm": 250,
|
|
255
|
+
"250": 250,
|
|
256
|
+
"base": 8453,
|
|
257
|
+
"base-mainnet": 8453,
|
|
258
|
+
"8453": 8453,
|
|
259
|
+
"arbitrum": 42161,
|
|
260
|
+
"arb": 42161,
|
|
261
|
+
"42161": 42161,
|
|
262
|
+
"avalanche": 43114,
|
|
263
|
+
"avax": 43114,
|
|
264
|
+
"43114": 43114,
|
|
265
|
+
"linea": 59144,
|
|
266
|
+
"59144": 59144,
|
|
267
|
+
"zksync": 324,
|
|
268
|
+
"324": 324,
|
|
269
|
+
"polygon-zkevm": 1101,
|
|
270
|
+
"1101": 1101,
|
|
271
|
+
"scroll": 534352,
|
|
272
|
+
"534352": 534352,
|
|
273
|
+
# Testnet chains
|
|
274
|
+
"ethereum-sepolia": 11155111,
|
|
275
|
+
"sepolia": 11155111,
|
|
276
|
+
"11155111": 11155111,
|
|
277
|
+
"base-sepolia": 84532,
|
|
278
|
+
"84532": 84532,
|
|
279
|
+
"arbitrum-sepolia": 421614,
|
|
280
|
+
"421614": 421614,
|
|
281
|
+
"optimism-sepolia": 11155420,
|
|
282
|
+
"11155420": 11155420,
|
|
283
|
+
"polygon-mumbai": 80001,
|
|
284
|
+
"mumbai": 80001,
|
|
285
|
+
"80001": 80001,
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
chain_lower = chain.lower()
|
|
289
|
+
if chain_lower in chain_mapping:
|
|
290
|
+
return chain_mapping[chain_lower]
|
|
291
|
+
|
|
292
|
+
raise ValueError(f"Unsupported chain identifier: {chain}")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def convert_amount_to_wei(amount: str, token_symbol: str = "ETH") -> str:
|
|
296
|
+
"""
|
|
297
|
+
Convert human-readable amount to wei format for LiFi API.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
amount: Amount in human readable format (e.g., "0.0015")
|
|
301
|
+
token_symbol: Token symbol to determine decimals
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Amount in wei format as string
|
|
305
|
+
"""
|
|
306
|
+
# Default decimals for common tokens
|
|
307
|
+
token_decimals = {
|
|
308
|
+
"ETH": 18,
|
|
309
|
+
"USDC": 6,
|
|
310
|
+
"USDT": 6,
|
|
311
|
+
"DAI": 18,
|
|
312
|
+
"WETH": 18,
|
|
313
|
+
"MATIC": 18,
|
|
314
|
+
"BNB": 18,
|
|
315
|
+
"AVAX": 18,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
decimals = token_decimals.get(token_symbol.upper(), 18)
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
# Convert string to float, then to wei
|
|
322
|
+
amount_float = float(amount)
|
|
323
|
+
amount_wei = int(amount_float * (10**decimals))
|
|
324
|
+
return str(amount_wei)
|
|
325
|
+
except (ValueError, TypeError):
|
|
326
|
+
# If conversion fails, return original amount
|
|
327
|
+
return amount
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def build_quote_params(
|
|
331
|
+
from_chain: str,
|
|
332
|
+
to_chain: str,
|
|
333
|
+
from_token: str,
|
|
334
|
+
to_token: str,
|
|
335
|
+
from_amount: str,
|
|
336
|
+
slippage: float,
|
|
337
|
+
from_address: Optional[str] = None,
|
|
338
|
+
) -> Dict[str, Any]:
|
|
339
|
+
"""
|
|
340
|
+
Build parameters for LiFi quote API request.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
from_chain, to_chain, from_token, to_token, from_amount: Transfer parameters
|
|
344
|
+
slippage: Slippage tolerance
|
|
345
|
+
from_address: Wallet address (uses dummy if None)
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
Dictionary of API parameters
|
|
349
|
+
|
|
350
|
+
Raises:
|
|
351
|
+
ValueError: If chain identifiers are not recognized
|
|
352
|
+
"""
|
|
353
|
+
# Convert amount to wei format for API
|
|
354
|
+
wei_amount = convert_amount_to_wei(from_amount, from_token)
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
"fromChain": convert_chain_to_id(from_chain),
|
|
358
|
+
"toChain": convert_chain_to_id(to_chain),
|
|
359
|
+
"fromToken": from_token,
|
|
360
|
+
"toToken": to_token,
|
|
361
|
+
"fromAmount": wei_amount,
|
|
362
|
+
"fromAddress": from_address or DUMMY_ADDRESS,
|
|
363
|
+
"slippage": slippage,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def is_native_token(token_address: str) -> bool:
|
|
368
|
+
"""
|
|
369
|
+
Check if token address represents a native token (ETH, MATIC, etc).
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
token_address: Token contract address
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
True if native token, False if ERC20
|
|
376
|
+
"""
|
|
377
|
+
return (
|
|
378
|
+
token_address == "0x0000000000000000000000000000000000000000"
|
|
379
|
+
or token_address == ""
|
|
380
|
+
or token_address.lower() == "0x0"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def prepare_transaction_params(transaction_request: Dict[str, Any]) -> Dict[str, Any]:
|
|
385
|
+
"""
|
|
386
|
+
Prepare transaction parameters for CDP wallet provider.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
transaction_request: Transaction request from LiFi API
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Formatted transaction parameters
|
|
393
|
+
|
|
394
|
+
Raises:
|
|
395
|
+
Exception: If required parameters are missing
|
|
396
|
+
"""
|
|
397
|
+
to_address = transaction_request.get("to")
|
|
398
|
+
value = transaction_request.get("value", "0")
|
|
399
|
+
data = transaction_request.get("data", "0x")
|
|
400
|
+
|
|
401
|
+
if not to_address:
|
|
402
|
+
raise Exception("No destination address in transaction request")
|
|
403
|
+
|
|
404
|
+
# Convert value to integer if it's a string
|
|
405
|
+
if isinstance(value, str):
|
|
406
|
+
value = int(value, 16) if value.startswith("0x") else int(value)
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
"to": Web3.to_checksum_address(to_address),
|
|
410
|
+
"value": value,
|
|
411
|
+
"data": data,
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def format_quote_basic_info(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
416
|
+
"""
|
|
417
|
+
Extract and format basic quote information.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
data: Quote response from LiFi API
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Dictionary with formatted basic info
|
|
424
|
+
"""
|
|
425
|
+
action = data.get("action", {})
|
|
426
|
+
estimate = data.get("estimate", {})
|
|
427
|
+
|
|
428
|
+
from_token_info = action.get("fromToken", {})
|
|
429
|
+
to_token_info = action.get("toToken", {})
|
|
430
|
+
|
|
431
|
+
from_amount = action.get("fromAmount", "0")
|
|
432
|
+
to_amount = estimate.get("toAmount", "0")
|
|
433
|
+
to_amount_min = estimate.get("toAmountMin", "0")
|
|
434
|
+
|
|
435
|
+
from_token_decimals = from_token_info.get("decimals", 18)
|
|
436
|
+
to_token_decimals = to_token_info.get("decimals", 18)
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
"from_token": from_token_info.get("symbol", "Unknown"),
|
|
440
|
+
"to_token": to_token_info.get("symbol", "Unknown"),
|
|
441
|
+
"from_chain": get_chain_name(action.get("fromChainId")),
|
|
442
|
+
"to_chain": get_chain_name(action.get("toChainId")),
|
|
443
|
+
"from_amount": format_amount(from_amount, from_token_decimals),
|
|
444
|
+
"to_amount": format_amount(to_amount, to_token_decimals),
|
|
445
|
+
"to_amount_min": format_amount(to_amount_min, to_token_decimals),
|
|
446
|
+
"tool": data.get("tool", "Unknown"),
|
|
447
|
+
"from_amount_usd": estimate.get("fromAmountUSD"),
|
|
448
|
+
"to_amount_usd": estimate.get("toAmountUSD"),
|
|
449
|
+
"execution_duration": estimate.get("executionDuration"),
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def format_fees_and_gas(data: Dict[str, Any]) -> Tuple[str, str]:
|
|
454
|
+
"""
|
|
455
|
+
Format fee and gas cost information from quote data.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
data: Quote response from LiFi API
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
Tuple of (fees_text, gas_text)
|
|
462
|
+
"""
|
|
463
|
+
estimate = data.get("estimate", {})
|
|
464
|
+
|
|
465
|
+
# Extract gas and fee costs
|
|
466
|
+
gas_costs = estimate.get("gasCosts", [])
|
|
467
|
+
fee_costs = []
|
|
468
|
+
|
|
469
|
+
# Collect fee information from included steps
|
|
470
|
+
for step in data.get("includedSteps", []):
|
|
471
|
+
step_fees = step.get("estimate", {}).get("feeCosts", [])
|
|
472
|
+
if step_fees:
|
|
473
|
+
fee_costs.extend(step_fees)
|
|
474
|
+
|
|
475
|
+
# Format fees
|
|
476
|
+
fees_text = ""
|
|
477
|
+
if fee_costs:
|
|
478
|
+
fees_text = "**Fees:**\n"
|
|
479
|
+
total_fee_usd = 0
|
|
480
|
+
for fee in fee_costs:
|
|
481
|
+
fee_name = fee.get("name", "Unknown fee")
|
|
482
|
+
fee_amount = fee.get("amount", "0")
|
|
483
|
+
fee_token = fee.get("token", {}).get("symbol", "")
|
|
484
|
+
fee_decimals = fee.get("token", {}).get("decimals", 18)
|
|
485
|
+
fee_percentage = fee.get("percentage", "0")
|
|
486
|
+
fee_usd = fee.get("amountUSD", "0")
|
|
487
|
+
|
|
488
|
+
fee_amount_formatted = format_amount(fee_amount, fee_decimals)
|
|
489
|
+
percentage_str = (
|
|
490
|
+
f" ({float(fee_percentage) * 100:.3f}%)"
|
|
491
|
+
if fee_percentage != "0"
|
|
492
|
+
else ""
|
|
493
|
+
)
|
|
494
|
+
fees_text += (
|
|
495
|
+
f"- {fee_name}: {fee_amount_formatted} {fee_token}{percentage_str}"
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
if fee_usd and float(fee_usd) > 0:
|
|
499
|
+
fees_text += f" (${fee_usd})"
|
|
500
|
+
total_fee_usd += float(fee_usd)
|
|
501
|
+
|
|
502
|
+
fees_text += "\n"
|
|
503
|
+
|
|
504
|
+
if total_fee_usd > 0:
|
|
505
|
+
fees_text += f"- **Total Fees:** ~${total_fee_usd:.4f}\n"
|
|
506
|
+
|
|
507
|
+
# Format gas costs
|
|
508
|
+
gas_text = ""
|
|
509
|
+
if gas_costs:
|
|
510
|
+
gas_text = "**Gas Cost:**\n"
|
|
511
|
+
total_gas_usd = 0
|
|
512
|
+
for gas in gas_costs:
|
|
513
|
+
gas_amount = gas.get("amount", "0")
|
|
514
|
+
gas_token = gas.get("token", {}).get("symbol", "ETH")
|
|
515
|
+
gas_decimals = gas.get("token", {}).get("decimals", 18)
|
|
516
|
+
gas_usd = gas.get("amountUSD", "0")
|
|
517
|
+
gas_type = gas.get("type", "SEND")
|
|
518
|
+
|
|
519
|
+
gas_amount_formatted = format_amount(gas_amount, gas_decimals)
|
|
520
|
+
gas_text += f"- {gas_type}: {gas_amount_formatted} {gas_token}"
|
|
521
|
+
|
|
522
|
+
if gas_usd and float(gas_usd) > 0:
|
|
523
|
+
gas_text += f" (${gas_usd})"
|
|
524
|
+
total_gas_usd += float(gas_usd)
|
|
525
|
+
|
|
526
|
+
gas_text += "\n"
|
|
527
|
+
|
|
528
|
+
if total_gas_usd > 0:
|
|
529
|
+
gas_text += f"- **Total Gas:** ~${total_gas_usd:.4f}\n"
|
|
530
|
+
|
|
531
|
+
return fees_text, gas_text
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def format_route_info(data: Dict[str, Any]) -> str:
|
|
535
|
+
"""
|
|
536
|
+
Format routing information from quote data.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
data: Quote response from LiFi API
|
|
540
|
+
|
|
541
|
+
Returns:
|
|
542
|
+
Formatted route information text
|
|
543
|
+
"""
|
|
544
|
+
included_steps = data.get("includedSteps", [])
|
|
545
|
+
if len(included_steps) <= 1:
|
|
546
|
+
return ""
|
|
547
|
+
|
|
548
|
+
route_text = "**Route:**\n"
|
|
549
|
+
for i, step in enumerate(included_steps, 1):
|
|
550
|
+
step_tool = step.get("tool", "Unknown")
|
|
551
|
+
step_type = step.get("type", "unknown")
|
|
552
|
+
route_text += f"{i}. {step_tool} ({step_type})\n"
|
|
553
|
+
|
|
554
|
+
return route_text
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def create_erc20_approve_data(spender_address: str, amount: str) -> str:
|
|
558
|
+
"""
|
|
559
|
+
Create encoded data for ERC20 approve function call.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
spender_address: Address to approve
|
|
563
|
+
amount: Amount to approve
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
Encoded function call data
|
|
567
|
+
"""
|
|
568
|
+
contract = Web3().eth.contract(
|
|
569
|
+
address=Web3.to_checksum_address("0x0000000000000000000000000000000000000000"),
|
|
570
|
+
abi=ERC20_ABI,
|
|
571
|
+
)
|
|
572
|
+
return contract.encode_abi("approve", [spender_address, int(amount)])
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def get_api_error_message(response: httpx.Response) -> str:
|
|
576
|
+
"""
|
|
577
|
+
Extract error message from API response.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
response: HTTP response
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
Formatted error message
|
|
584
|
+
"""
|
|
585
|
+
try:
|
|
586
|
+
error_data = response.json()
|
|
587
|
+
return error_data.get("message", response.text)
|
|
588
|
+
except (ValueError, TypeError, AttributeError):
|
|
589
|
+
return response.text
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def get_explorer_url(chain_id: int, tx_hash: str) -> str:
|
|
593
|
+
"""
|
|
594
|
+
Generate blockchain explorer URL for a transaction.
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
chain_id: Blockchain chain ID
|
|
598
|
+
tx_hash: Transaction hash
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
Explorer URL for the transaction
|
|
602
|
+
"""
|
|
603
|
+
# Explorer URLs for different chains
|
|
604
|
+
explorers = {
|
|
605
|
+
1: "https://etherscan.io/tx/", # Ethereum
|
|
606
|
+
10: "https://optimistic.etherscan.io/tx/", # Optimism
|
|
607
|
+
56: "https://bscscan.com/tx/", # BSC
|
|
608
|
+
100: "https://gnosisscan.io/tx/", # Gnosis
|
|
609
|
+
137: "https://polygonscan.com/tx/", # Polygon
|
|
610
|
+
250: "https://ftmscan.com/tx/", # Fantom
|
|
611
|
+
8453: "https://basescan.org/tx/", # Base
|
|
612
|
+
42161: "https://arbiscan.io/tx/", # Arbitrum
|
|
613
|
+
43114: "https://snowtrace.io/tx/", # Avalanche
|
|
614
|
+
59144: "https://lineascan.build/tx/", # Linea
|
|
615
|
+
324: "https://explorer.zksync.io/tx/", # zkSync Era
|
|
616
|
+
1101: "https://zkevm.polygonscan.com/tx/", # Polygon zkEVM
|
|
617
|
+
534352: "https://scrollscan.com/tx/", # Scroll
|
|
618
|
+
# Testnet explorers
|
|
619
|
+
11155111: "https://sepolia.etherscan.io/tx/", # Ethereum Sepolia
|
|
620
|
+
84532: "https://sepolia.basescan.org/tx/", # Base Sepolia
|
|
621
|
+
421614: "https://sepolia.arbiscan.io/tx/", # Arbitrum Sepolia
|
|
622
|
+
11155420: "https://sepolia-optimism.etherscan.io/tx/", # Optimism Sepolia
|
|
623
|
+
80001: "https://mumbai.polygonscan.com/tx/", # Polygon Mumbai
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
base_url = explorers.get(chain_id, "https://etherscan.io/tx/")
|
|
627
|
+
return f"{base_url}{tx_hash}"
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def format_transaction_result(
|
|
631
|
+
tx_hash: str, chain_id: int, token_info: dict = None
|
|
632
|
+
) -> str:
|
|
633
|
+
"""
|
|
634
|
+
Format transaction result with explorer link.
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
tx_hash: Transaction hash
|
|
638
|
+
chain_id: Chain ID where transaction was executed
|
|
639
|
+
token_info: Optional token information for context
|
|
640
|
+
|
|
641
|
+
Returns:
|
|
642
|
+
Formatted transaction result message
|
|
643
|
+
"""
|
|
644
|
+
explorer_url = get_explorer_url(chain_id, tx_hash)
|
|
645
|
+
chain_name = get_chain_name(chain_id)
|
|
646
|
+
|
|
647
|
+
result = "Transaction successful!\n"
|
|
648
|
+
result += f"Transaction Hash: {tx_hash}\n"
|
|
649
|
+
result += f"Network: {chain_name}\n"
|
|
650
|
+
result += f"Explorer: {explorer_url}\n"
|
|
651
|
+
|
|
652
|
+
if token_info:
|
|
653
|
+
result += f"Token: {token_info.get('symbol', 'Unknown')}\n"
|
|
654
|
+
result += f"Amount: {token_info.get('amount', 'Unknown')}\n"
|
|
655
|
+
|
|
656
|
+
return result
|