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,284 @@
|
|
|
1
|
+
"""fetching NFT portfolio for a wallet."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any, Dict, List, Optional, Type
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from intentkit.skills.moralis.api import fetch_nft_data, get_solana_nfts
|
|
10
|
+
from intentkit.skills.moralis.base import WalletBaseTool
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FetchNftPortfolioInput(BaseModel):
|
|
16
|
+
"""Input for FetchNftPortfolio tool."""
|
|
17
|
+
|
|
18
|
+
address: str = Field(..., description="Wallet address")
|
|
19
|
+
chain_id: Optional[int] = Field(
|
|
20
|
+
None,
|
|
21
|
+
description="Chain ID (if not specified, fetches from all supported chains)",
|
|
22
|
+
)
|
|
23
|
+
include_solana: bool = Field(
|
|
24
|
+
default=False, description="Whether to include Solana NFTs"
|
|
25
|
+
)
|
|
26
|
+
solana_network: str = Field(
|
|
27
|
+
default="mainnet", description="Solana network to use (mainnet or devnet)"
|
|
28
|
+
)
|
|
29
|
+
limit: Optional[int] = Field(100, description="Maximum number of NFTs to return")
|
|
30
|
+
normalize_metadata: bool = Field(
|
|
31
|
+
True, description="Whether to normalize metadata across different standards"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class NftMetadata(BaseModel):
|
|
36
|
+
"""Model for NFT metadata."""
|
|
37
|
+
|
|
38
|
+
name: Optional[str] = Field(None, description="NFT name")
|
|
39
|
+
description: Optional[str] = Field(None, description="NFT description")
|
|
40
|
+
image: Optional[str] = Field(None, description="NFT image URL")
|
|
41
|
+
animation_url: Optional[str] = Field(None, description="NFT animation URL")
|
|
42
|
+
attributes: Optional[List[Dict]] = Field(None, description="NFT attributes/traits")
|
|
43
|
+
external_url: Optional[str] = Field(None, description="External URL")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class NftItem(BaseModel):
|
|
47
|
+
"""Model for an NFT item."""
|
|
48
|
+
|
|
49
|
+
token_id: str = Field(..., description="NFT token ID")
|
|
50
|
+
token_address: str = Field(..., description="NFT contract address")
|
|
51
|
+
contract_type: Optional[str] = Field(
|
|
52
|
+
None, description="NFT contract type (ERC721, ERC1155, etc.)"
|
|
53
|
+
)
|
|
54
|
+
name: Optional[str] = Field(None, description="NFT name")
|
|
55
|
+
symbol: Optional[str] = Field(None, description="NFT symbol")
|
|
56
|
+
owner_of: str = Field(..., description="Owner address")
|
|
57
|
+
metadata: Optional[NftMetadata] = Field(None, description="NFT metadata")
|
|
58
|
+
floor_price: Optional[float] = Field(None, description="Floor price if available")
|
|
59
|
+
chain: str = Field("eth", description="Blockchain network")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class NftPortfolioOutput(BaseModel):
|
|
63
|
+
"""Output for FetchNftPortfolio tool."""
|
|
64
|
+
|
|
65
|
+
address: str = Field(..., description="Wallet address")
|
|
66
|
+
nfts: List[NftItem] = Field(default_factory=list, description="List of NFT items")
|
|
67
|
+
total_count: int = Field(0, description="Total count of NFTs")
|
|
68
|
+
chains: List[str] = Field(
|
|
69
|
+
default_factory=list, description="Chains included in the response"
|
|
70
|
+
)
|
|
71
|
+
cursor: Optional[str] = Field(None, description="Cursor for pagination")
|
|
72
|
+
error: Optional[str] = Field(None, description="Error message if any")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class FetchNftPortfolio(WalletBaseTool):
|
|
76
|
+
"""Tool for fetching NFT portfolio for a wallet.
|
|
77
|
+
|
|
78
|
+
This tool retrieves detailed information about NFTs owned by a wallet address,
|
|
79
|
+
including metadata, media URLs, and floor prices when available.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
name: str = "moralis_fetch_nft_portfolio"
|
|
83
|
+
description: str = (
|
|
84
|
+
"This tool fetches NFT holdings for a wallet address.\n"
|
|
85
|
+
"Provide a wallet address and optionally a chain ID to get detailed information about NFTs.\n"
|
|
86
|
+
"Returns:\n"
|
|
87
|
+
"- NFT collection data\n"
|
|
88
|
+
"- NFT metadata and attributes\n"
|
|
89
|
+
"- Media URLs if available\n"
|
|
90
|
+
"- Floor prices if available\n"
|
|
91
|
+
"Use this tool whenever a user asks about their NFTs or digital collectibles."
|
|
92
|
+
)
|
|
93
|
+
args_schema: Type[BaseModel] = FetchNftPortfolioInput
|
|
94
|
+
|
|
95
|
+
async def _arun(
|
|
96
|
+
self,
|
|
97
|
+
address: str,
|
|
98
|
+
chain_id: Optional[int] = None,
|
|
99
|
+
include_solana: bool = False,
|
|
100
|
+
solana_network: str = "mainnet",
|
|
101
|
+
limit: int = 100,
|
|
102
|
+
normalize_metadata: bool = True,
|
|
103
|
+
**kwargs,
|
|
104
|
+
) -> NftPortfolioOutput:
|
|
105
|
+
"""Fetch NFT portfolio for a wallet.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
address: Wallet address to fetch NFTs for
|
|
109
|
+
chain_id: Chain ID to fetch NFTs for (if None, fetches from all supported chains)
|
|
110
|
+
include_solana: Whether to include Solana NFTs
|
|
111
|
+
solana_network: Solana network to use (mainnet or devnet)
|
|
112
|
+
limit: Maximum number of NFTs to return
|
|
113
|
+
normalize_metadata: Whether to normalize metadata across different standards
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
NftPortfolioOutput containing NFT portfolio data
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
# Initialize result
|
|
120
|
+
result = {"address": address, "nfts": [], "total_count": 0, "chains": []}
|
|
121
|
+
|
|
122
|
+
# Fetch EVM NFTs
|
|
123
|
+
if chain_id is not None:
|
|
124
|
+
# Fetch from specific chain
|
|
125
|
+
await self._fetch_evm_nfts(
|
|
126
|
+
address, chain_id, limit, normalize_metadata, result
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
# Fetch from all supported chains
|
|
130
|
+
from intentkit.skills.moralis.base import CHAIN_MAPPING
|
|
131
|
+
|
|
132
|
+
for chain_id in CHAIN_MAPPING.keys():
|
|
133
|
+
await self._fetch_evm_nfts(
|
|
134
|
+
address,
|
|
135
|
+
chain_id,
|
|
136
|
+
limit // len(CHAIN_MAPPING),
|
|
137
|
+
normalize_metadata,
|
|
138
|
+
result,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Fetch Solana NFTs if requested
|
|
142
|
+
if include_solana:
|
|
143
|
+
await self._fetch_solana_nfts(address, solana_network, limit, result)
|
|
144
|
+
|
|
145
|
+
return NftPortfolioOutput(**result)
|
|
146
|
+
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"Error fetching NFT portfolio: {str(e)}")
|
|
149
|
+
return NftPortfolioOutput(
|
|
150
|
+
address=address, nfts=[], total_count=0, chains=[], error=str(e)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
async def _fetch_evm_nfts(
|
|
154
|
+
self,
|
|
155
|
+
address: str,
|
|
156
|
+
chain_id: int,
|
|
157
|
+
limit: int,
|
|
158
|
+
normalize_metadata: bool,
|
|
159
|
+
result: Dict[str, Any],
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Fetch NFTs from an EVM chain.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
address: Wallet address
|
|
165
|
+
chain_id: Chain ID
|
|
166
|
+
limit: Maximum number of NFTs to return
|
|
167
|
+
normalize_metadata: Whether to normalize metadata
|
|
168
|
+
result: Result dictionary to update
|
|
169
|
+
"""
|
|
170
|
+
params = {"limit": limit, "normalizeMetadata": normalize_metadata}
|
|
171
|
+
|
|
172
|
+
nft_data = await fetch_nft_data(self.api_key, address, chain_id, params)
|
|
173
|
+
|
|
174
|
+
if "error" in nft_data:
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
chain_name = self._get_chain_name(chain_id)
|
|
178
|
+
if chain_name not in result["chains"]:
|
|
179
|
+
result["chains"].append(chain_name)
|
|
180
|
+
|
|
181
|
+
result["total_count"] += nft_data.get("total", 0)
|
|
182
|
+
|
|
183
|
+
if "cursor" in nft_data:
|
|
184
|
+
result["cursor"] = nft_data["cursor"]
|
|
185
|
+
|
|
186
|
+
for nft in nft_data.get("result", []):
|
|
187
|
+
# Extract metadata
|
|
188
|
+
metadata = None
|
|
189
|
+
if "metadata" in nft and nft["metadata"]:
|
|
190
|
+
try:
|
|
191
|
+
if isinstance(nft["metadata"], str):
|
|
192
|
+
metadata_dict = json.loads(nft["metadata"])
|
|
193
|
+
else:
|
|
194
|
+
metadata_dict = nft["metadata"]
|
|
195
|
+
|
|
196
|
+
metadata = NftMetadata(
|
|
197
|
+
name=metadata_dict.get("name"),
|
|
198
|
+
description=metadata_dict.get("description"),
|
|
199
|
+
image=metadata_dict.get("image"),
|
|
200
|
+
animation_url=metadata_dict.get("animation_url"),
|
|
201
|
+
attributes=metadata_dict.get("attributes"),
|
|
202
|
+
external_url=metadata_dict.get("external_url"),
|
|
203
|
+
)
|
|
204
|
+
except Exception as e:
|
|
205
|
+
logger.warning(f"Error parsing NFT metadata: {str(e)}")
|
|
206
|
+
# If metadata parsing fails, continue without it
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
# Create NFT item
|
|
210
|
+
nft_item = NftItem(
|
|
211
|
+
token_id=nft.get("token_id", ""),
|
|
212
|
+
token_address=nft.get("token_address", ""),
|
|
213
|
+
contract_type=nft.get("contract_type"),
|
|
214
|
+
name=nft.get("name"),
|
|
215
|
+
symbol=nft.get("symbol"),
|
|
216
|
+
owner_of=nft.get("owner_of", address),
|
|
217
|
+
metadata=metadata,
|
|
218
|
+
floor_price=nft.get("floor_price"),
|
|
219
|
+
chain=chain_name,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
result["nfts"].append(nft_item)
|
|
223
|
+
|
|
224
|
+
async def _fetch_solana_nfts(
|
|
225
|
+
self, address: str, network: str, limit: int, result: Dict[str, Any]
|
|
226
|
+
) -> None:
|
|
227
|
+
"""Fetch NFTs from Solana.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
address: Wallet address
|
|
231
|
+
network: Solana network
|
|
232
|
+
limit: Maximum number of NFTs to return
|
|
233
|
+
result: Result dictionary to update
|
|
234
|
+
"""
|
|
235
|
+
chain_name = "solana"
|
|
236
|
+
if chain_name not in result["chains"]:
|
|
237
|
+
result["chains"].append(chain_name)
|
|
238
|
+
|
|
239
|
+
nfts_result = await get_solana_nfts(self.api_key, address, network)
|
|
240
|
+
|
|
241
|
+
if "error" in nfts_result:
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
if not isinstance(nfts_result, list):
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
count = min(limit, len(nfts_result))
|
|
248
|
+
result["total_count"] += count
|
|
249
|
+
|
|
250
|
+
for i, nft in enumerate(nfts_result):
|
|
251
|
+
if i >= limit:
|
|
252
|
+
break
|
|
253
|
+
|
|
254
|
+
# Create NFT item
|
|
255
|
+
metadata = None
|
|
256
|
+
if "metadata" in nft and nft["metadata"]:
|
|
257
|
+
try:
|
|
258
|
+
metadata_dict = nft["metadata"]
|
|
259
|
+
if isinstance(metadata_dict, str):
|
|
260
|
+
metadata_dict = json.loads(metadata_dict)
|
|
261
|
+
|
|
262
|
+
metadata = NftMetadata(
|
|
263
|
+
name=metadata_dict.get("name"),
|
|
264
|
+
description=metadata_dict.get("description"),
|
|
265
|
+
image=metadata_dict.get("image"),
|
|
266
|
+
animation_url=metadata_dict.get("animation_url"),
|
|
267
|
+
attributes=metadata_dict.get("attributes"),
|
|
268
|
+
external_url=metadata_dict.get("external_url"),
|
|
269
|
+
)
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.warning(f"Error parsing Solana NFT metadata: {str(e)}")
|
|
272
|
+
pass
|
|
273
|
+
|
|
274
|
+
nft_item = NftItem(
|
|
275
|
+
token_id=nft.get("mint", ""), # Use mint address as token ID
|
|
276
|
+
token_address=nft.get("mint", ""), # Use mint address as token address
|
|
277
|
+
name=nft.get("name"),
|
|
278
|
+
symbol=nft.get("symbol"),
|
|
279
|
+
owner_of=address,
|
|
280
|
+
metadata=metadata,
|
|
281
|
+
chain=chain_name,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
result["nfts"].append(nft_item)
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""fetching Solana wallet portfolio."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Dict, List, Optional, Type
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from intentkit.skills.moralis.api import (
|
|
9
|
+
get_solana_balance,
|
|
10
|
+
get_solana_nfts,
|
|
11
|
+
get_solana_portfolio,
|
|
12
|
+
get_solana_spl_tokens,
|
|
13
|
+
get_token_price,
|
|
14
|
+
)
|
|
15
|
+
from intentkit.skills.moralis.base import WalletBaseTool
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SolanaPortfolioInput(BaseModel):
|
|
21
|
+
"""Input for FetchSolanaPortfolio tool."""
|
|
22
|
+
|
|
23
|
+
address: str = Field(..., description="Solana wallet address")
|
|
24
|
+
network: str = Field(
|
|
25
|
+
default="mainnet", description="Solana network to use (mainnet or devnet)"
|
|
26
|
+
)
|
|
27
|
+
include_nfts: bool = Field(
|
|
28
|
+
default=False, description="Whether to include NFTs in the response"
|
|
29
|
+
)
|
|
30
|
+
include_price_data: bool = Field(
|
|
31
|
+
default=True, description="Whether to include price data for tokens"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SolanaTokenInfo(BaseModel):
|
|
36
|
+
"""Model for Solana token information."""
|
|
37
|
+
|
|
38
|
+
symbol: str
|
|
39
|
+
name: str
|
|
40
|
+
decimals: int
|
|
41
|
+
mint: str
|
|
42
|
+
associated_token_address: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SolanaTokenBalance(BaseModel):
|
|
46
|
+
"""Model for Solana token balance."""
|
|
47
|
+
|
|
48
|
+
token_info: SolanaTokenInfo
|
|
49
|
+
amount: float
|
|
50
|
+
amount_raw: str
|
|
51
|
+
usd_value: Optional[float] = 0.0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SolanaNftInfo(BaseModel):
|
|
55
|
+
"""Model for Solana NFT information."""
|
|
56
|
+
|
|
57
|
+
mint: str
|
|
58
|
+
name: Optional[str] = None
|
|
59
|
+
symbol: Optional[str] = None
|
|
60
|
+
associated_token_address: str
|
|
61
|
+
metadata: Optional[Dict] = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SolanaPortfolioOutput(BaseModel):
|
|
65
|
+
"""Output for FetchSolanaPortfolio tool."""
|
|
66
|
+
|
|
67
|
+
address: str
|
|
68
|
+
sol_balance: float
|
|
69
|
+
sol_balance_lamports: int
|
|
70
|
+
sol_price_usd: Optional[float] = None
|
|
71
|
+
sol_value_usd: Optional[float] = None
|
|
72
|
+
tokens: List[SolanaTokenBalance] = []
|
|
73
|
+
nfts: List[SolanaNftInfo] = []
|
|
74
|
+
total_value_usd: float = 0.0
|
|
75
|
+
error: Optional[str] = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class FetchSolanaPortfolio(WalletBaseTool):
|
|
79
|
+
"""Tool for fetching Solana wallet portfolio.
|
|
80
|
+
|
|
81
|
+
This tool retrieves detailed information about a Solana wallet's holdings,
|
|
82
|
+
including native SOL, SPL tokens, and optionally NFTs.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
name: str = "moralis_fetch_solana_portfolio"
|
|
86
|
+
description: str = (
|
|
87
|
+
"Get comprehensive portfolio data for a Solana wallet including:\n"
|
|
88
|
+
"- Native SOL balance\n"
|
|
89
|
+
"- SPL token balances\n"
|
|
90
|
+
"- NFT holdings (optional)\n"
|
|
91
|
+
"- USD values of assets\n"
|
|
92
|
+
"Use this tool whenever the user asks specifically about Solana holdings."
|
|
93
|
+
)
|
|
94
|
+
args_schema: Type[BaseModel] = SolanaPortfolioInput
|
|
95
|
+
|
|
96
|
+
async def _arun(
|
|
97
|
+
self,
|
|
98
|
+
address: str,
|
|
99
|
+
network: str = "mainnet",
|
|
100
|
+
include_nfts: bool = False,
|
|
101
|
+
include_price_data: bool = True,
|
|
102
|
+
**kwargs,
|
|
103
|
+
) -> SolanaPortfolioOutput:
|
|
104
|
+
"""Fetch Solana wallet portfolio data.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
address: Solana wallet address
|
|
108
|
+
network: Solana network to use (mainnet or devnet)
|
|
109
|
+
include_nfts: Whether to include NFTs in the response
|
|
110
|
+
include_price_data: Whether to include price data for tokens
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
SolanaPortfolioOutput containing the Solana wallet's portfolio data
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
# Try to get complete portfolio
|
|
117
|
+
sol_portfolio = await get_solana_portfolio(self.api_key, address, network)
|
|
118
|
+
|
|
119
|
+
if "error" not in sol_portfolio:
|
|
120
|
+
return await self._process_portfolio_data(
|
|
121
|
+
address, network, sol_portfolio, include_nfts, include_price_data
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
# If portfolio endpoint fails, try to fetch data separately
|
|
125
|
+
return await self._fetch_separate_portfolio_data(
|
|
126
|
+
address, network, include_nfts, include_price_data
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"Error fetching Solana portfolio: {str(e)}")
|
|
131
|
+
return SolanaPortfolioOutput(
|
|
132
|
+
address=address, sol_balance=0, sol_balance_lamports=0, error=str(e)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def _process_portfolio_data(
|
|
136
|
+
self,
|
|
137
|
+
address: str,
|
|
138
|
+
network: str,
|
|
139
|
+
sol_portfolio: Dict,
|
|
140
|
+
include_nfts: bool,
|
|
141
|
+
include_price_data: bool,
|
|
142
|
+
) -> SolanaPortfolioOutput:
|
|
143
|
+
"""Process portfolio data from the API.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
address: Solana wallet address
|
|
147
|
+
network: Solana network
|
|
148
|
+
sol_portfolio: Portfolio data from the API
|
|
149
|
+
include_nfts: Whether to include NFTs
|
|
150
|
+
include_price_data: Whether to include price data
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
SolanaPortfolioOutput with processed data
|
|
154
|
+
"""
|
|
155
|
+
result = SolanaPortfolioOutput(
|
|
156
|
+
address=address,
|
|
157
|
+
sol_balance=float(sol_portfolio.get("nativeBalance", {}).get("solana", 0)),
|
|
158
|
+
sol_balance_lamports=int(
|
|
159
|
+
sol_portfolio.get("nativeBalance", {}).get("lamports", 0)
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Process tokens
|
|
164
|
+
tokens = []
|
|
165
|
+
for token in sol_portfolio.get("tokens", []):
|
|
166
|
+
token_info = SolanaTokenInfo(
|
|
167
|
+
symbol=token.get("symbol", ""),
|
|
168
|
+
name=token.get("name", ""),
|
|
169
|
+
decimals=int(token.get("decimals", 0)),
|
|
170
|
+
mint=token.get("mint", ""),
|
|
171
|
+
associated_token_address=token.get("associatedTokenAddress", ""),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
token_balance = SolanaTokenBalance(
|
|
175
|
+
token_info=token_info,
|
|
176
|
+
amount=float(token.get("amount", 0)),
|
|
177
|
+
amount_raw=token.get("amountRaw", "0"),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
tokens.append(token_balance)
|
|
181
|
+
|
|
182
|
+
result.tokens = tokens
|
|
183
|
+
|
|
184
|
+
# Fetch NFTs if requested
|
|
185
|
+
if include_nfts:
|
|
186
|
+
nfts_result = await get_solana_nfts(self.api_key, address, network)
|
|
187
|
+
|
|
188
|
+
if "error" not in nfts_result and isinstance(nfts_result, list):
|
|
189
|
+
nfts = []
|
|
190
|
+
for nft in nfts_result:
|
|
191
|
+
nft_info = SolanaNftInfo(
|
|
192
|
+
mint=nft.get("mint", ""),
|
|
193
|
+
name=nft.get("name"),
|
|
194
|
+
symbol=nft.get("symbol"),
|
|
195
|
+
associated_token_address=nft.get("associatedTokenAddress", ""),
|
|
196
|
+
metadata=nft.get("metadata"),
|
|
197
|
+
)
|
|
198
|
+
nfts.append(nft_info)
|
|
199
|
+
|
|
200
|
+
result.nfts = nfts
|
|
201
|
+
|
|
202
|
+
# Fetch price data if requested
|
|
203
|
+
if include_price_data:
|
|
204
|
+
# Fetch SOL price
|
|
205
|
+
sol_price_result = await get_token_price(
|
|
206
|
+
self.api_key,
|
|
207
|
+
"So11111111111111111111111111111111111111112", # SOL mint address
|
|
208
|
+
network,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if "error" not in sol_price_result:
|
|
212
|
+
sol_price_usd = float(sol_price_result.get("usdPrice", 0))
|
|
213
|
+
result.sol_price_usd = sol_price_usd
|
|
214
|
+
result.sol_value_usd = sol_price_usd * result.sol_balance
|
|
215
|
+
result.total_value_usd += result.sol_value_usd or 0
|
|
216
|
+
|
|
217
|
+
# Fetch token prices
|
|
218
|
+
for token in result.tokens:
|
|
219
|
+
if token.token_info.mint:
|
|
220
|
+
price_result = await get_token_price(
|
|
221
|
+
self.api_key, token.token_info.mint, network
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if "error" not in price_result:
|
|
225
|
+
token_price_usd = float(price_result.get("usdPrice", 0))
|
|
226
|
+
token.usd_value = token_price_usd * token.amount
|
|
227
|
+
result.total_value_usd += token.usd_value
|
|
228
|
+
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
async def _fetch_separate_portfolio_data(
|
|
232
|
+
self, address: str, network: str, include_nfts: bool, include_price_data: bool
|
|
233
|
+
) -> SolanaPortfolioOutput:
|
|
234
|
+
"""Fetch portfolio data using separate API calls.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
address: Solana wallet address
|
|
238
|
+
network: Solana network
|
|
239
|
+
include_nfts: Whether to include NFTs
|
|
240
|
+
include_price_data: Whether to include price data
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
SolanaPortfolioOutput with processed data
|
|
244
|
+
"""
|
|
245
|
+
# Get SOL balance
|
|
246
|
+
balance_result = await get_solana_balance(self.api_key, address, network)
|
|
247
|
+
|
|
248
|
+
if "error" in balance_result:
|
|
249
|
+
return SolanaPortfolioOutput(
|
|
250
|
+
address=address,
|
|
251
|
+
sol_balance=0,
|
|
252
|
+
sol_balance_lamports=0,
|
|
253
|
+
error=balance_result["error"],
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
result = SolanaPortfolioOutput(
|
|
257
|
+
address=address,
|
|
258
|
+
sol_balance=float(balance_result.get("solana", 0)),
|
|
259
|
+
sol_balance_lamports=int(balance_result.get("lamports", 0)),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Get SPL tokens
|
|
263
|
+
tokens_result = await get_solana_spl_tokens(self.api_key, address, network)
|
|
264
|
+
|
|
265
|
+
if "error" not in tokens_result and isinstance(tokens_result, list):
|
|
266
|
+
tokens = []
|
|
267
|
+
for token in tokens_result:
|
|
268
|
+
token_info = SolanaTokenInfo(
|
|
269
|
+
symbol=token.get("symbol", ""),
|
|
270
|
+
name=token.get("name", ""),
|
|
271
|
+
decimals=int(token.get("decimals", 0)),
|
|
272
|
+
mint=token.get("mint", ""),
|
|
273
|
+
associated_token_address=token.get("associatedTokenAddress", ""),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
token_balance = SolanaTokenBalance(
|
|
277
|
+
token_info=token_info,
|
|
278
|
+
amount=float(token.get("amount", 0)),
|
|
279
|
+
amount_raw=token.get("amountRaw", "0"),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
tokens.append(token_balance)
|
|
283
|
+
|
|
284
|
+
result.tokens = tokens
|
|
285
|
+
|
|
286
|
+
# Fetch NFTs if requested
|
|
287
|
+
if include_nfts:
|
|
288
|
+
nfts_result = await get_solana_nfts(self.api_key, address, network)
|
|
289
|
+
|
|
290
|
+
if "error" not in nfts_result and isinstance(nfts_result, list):
|
|
291
|
+
nfts = []
|
|
292
|
+
for nft in nfts_result:
|
|
293
|
+
nft_info = SolanaNftInfo(
|
|
294
|
+
mint=nft.get("mint", ""),
|
|
295
|
+
name=nft.get("name"),
|
|
296
|
+
symbol=nft.get("symbol"),
|
|
297
|
+
associated_token_address=nft.get("associatedTokenAddress", ""),
|
|
298
|
+
metadata=nft.get("metadata"),
|
|
299
|
+
)
|
|
300
|
+
nfts.append(nft_info)
|
|
301
|
+
|
|
302
|
+
result.nfts = nfts
|
|
303
|
+
|
|
304
|
+
# Fetch price data if requested
|
|
305
|
+
if include_price_data:
|
|
306
|
+
# Fetch SOL price
|
|
307
|
+
sol_price_result = await get_token_price(
|
|
308
|
+
self.api_key,
|
|
309
|
+
"So11111111111111111111111111111111111111112", # SOL mint address
|
|
310
|
+
network,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
if "error" not in sol_price_result:
|
|
314
|
+
sol_price_usd = float(sol_price_result.get("usdPrice", 0))
|
|
315
|
+
result.sol_price_usd = sol_price_usd
|
|
316
|
+
result.sol_value_usd = sol_price_usd * result.sol_balance
|
|
317
|
+
result.total_value_usd += result.sol_value_usd or 0
|
|
318
|
+
|
|
319
|
+
# Fetch token prices
|
|
320
|
+
for token in result.tokens:
|
|
321
|
+
if token.token_info.mint:
|
|
322
|
+
price_result = await get_token_price(
|
|
323
|
+
self.api_key, token.token_info.mint, network
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if "error" not in price_result:
|
|
327
|
+
token_price_usd = float(price_result.get("usdPrice", 0))
|
|
328
|
+
token.usd_value = token_price_usd * token.amount
|
|
329
|
+
result.total_value_usd += token.usd_value
|
|
330
|
+
|
|
331
|
+
return result
|