keyring-agent-core 0.2.1 → 0.2.3
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.
- package/dist/index.d.ts +6692 -27
- package/dist/index.js +877 -70
- package/package.json +6 -4
- package/dist/agent/AgentCore.d.ts +0 -256
- package/dist/agent/AgentCore.d.ts.map +0 -1
- package/dist/agent/AgentCore.js +0 -1093
- package/dist/agent/AgentCore.js.map +0 -1
- package/dist/agent/QueryRewriter.d.ts +0 -84
- package/dist/agent/QueryRewriter.d.ts.map +0 -1
- package/dist/agent/QueryRewriter.js +0 -306
- package/dist/agent/QueryRewriter.js.map +0 -1
- package/dist/agent/Router.d.ts +0 -33
- package/dist/agent/Router.d.ts.map +0 -1
- package/dist/agent/Router.js +0 -229
- package/dist/agent/Router.js.map +0 -1
- package/dist/agent/Subagent.d.ts +0 -80
- package/dist/agent/Subagent.d.ts.map +0 -1
- package/dist/agent/Subagent.js +0 -272
- package/dist/agent/Subagent.js.map +0 -1
- package/dist/agent/Synthesizer.d.ts +0 -11
- package/dist/agent/Synthesizer.d.ts.map +0 -1
- package/dist/agent/Synthesizer.js +0 -86
- package/dist/agent/Synthesizer.js.map +0 -1
- package/dist/agent/chatGraph.d.ts +0 -364
- package/dist/agent/chatGraph.d.ts.map +0 -1
- package/dist/agent/chatGraph.js +0 -184
- package/dist/agent/chatGraph.js.map +0 -1
- package/dist/agent/index.d.ts +0 -10
- package/dist/agent/index.d.ts.map +0 -1
- package/dist/agent/index.js +0 -24
- package/dist/agent/index.js.map +0 -1
- package/dist/agent/subagents/AiAgent.d.ts +0 -6
- package/dist/agent/subagents/AiAgent.d.ts.map +0 -1
- package/dist/agent/subagents/AiAgent.js +0 -52
- package/dist/agent/subagents/AiAgent.js.map +0 -1
- package/dist/agent/subagents/NftAgent.d.ts +0 -6
- package/dist/agent/subagents/NftAgent.d.ts.map +0 -1
- package/dist/agent/subagents/NftAgent.js +0 -79
- package/dist/agent/subagents/NftAgent.js.map +0 -1
- package/dist/agent/subagents/PoolAgent.d.ts +0 -6
- package/dist/agent/subagents/PoolAgent.d.ts.map +0 -1
- package/dist/agent/subagents/PoolAgent.js +0 -184
- package/dist/agent/subagents/PoolAgent.js.map +0 -1
- package/dist/agent/subagents/PoolSubgraphAgent.d.ts +0 -6
- package/dist/agent/subagents/PoolSubgraphAgent.d.ts.map +0 -1
- package/dist/agent/subagents/PoolSubgraphAgent.js +0 -117
- package/dist/agent/subagents/PoolSubgraphAgent.js.map +0 -1
- package/dist/agent/subagents/TokenAgent.d.ts +0 -6
- package/dist/agent/subagents/TokenAgent.d.ts.map +0 -1
- package/dist/agent/subagents/TokenAgent.js +0 -76
- package/dist/agent/subagents/TokenAgent.js.map +0 -1
- package/dist/agent/subagents/WalletActionAgent.d.ts +0 -6
- package/dist/agent/subagents/WalletActionAgent.d.ts.map +0 -1
- package/dist/agent/subagents/WalletActionAgent.js +0 -55
- package/dist/agent/subagents/WalletActionAgent.js.map +0 -1
- package/dist/agent/subagents/WalletAgent.d.ts +0 -6
- package/dist/agent/subagents/WalletAgent.d.ts.map +0 -1
- package/dist/agent/subagents/WalletAgent.js +0 -93
- package/dist/agent/subagents/WalletAgent.js.map +0 -1
- package/dist/agent/subagents/index.d.ts +0 -34
- package/dist/agent/subagents/index.d.ts.map +0 -1
- package/dist/agent/subagents/index.js +0 -69
- package/dist/agent/subagents/index.js.map +0 -1
- package/dist/constants/abi.d.ts +0 -551
- package/dist/constants/abi.d.ts.map +0 -1
- package/dist/constants/abi.js +0 -690
- package/dist/constants/abi.js.map +0 -1
- package/dist/functions/pool.d.ts +0 -25
- package/dist/functions/pool.d.ts.map +0 -1
- package/dist/functions/pool.js +0 -151
- package/dist/functions/pool.js.map +0 -1
- package/dist/functions/web3.d.ts +0 -31
- package/dist/functions/web3.d.ts.map +0 -1
- package/dist/functions/web3.js +0 -232
- package/dist/functions/web3.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/llm/GeminiProvider.d.ts +0 -16
- package/dist/llm/GeminiProvider.d.ts.map +0 -1
- package/dist/llm/GeminiProvider.js +0 -329
- package/dist/llm/GeminiProvider.js.map +0 -1
- package/dist/llm/index.d.ts +0 -2
- package/dist/llm/index.d.ts.map +0 -1
- package/dist/llm/index.js +0 -6
- package/dist/llm/index.js.map +0 -1
- package/dist/memory/ChatHistory.d.ts +0 -83
- package/dist/memory/ChatHistory.d.ts.map +0 -1
- package/dist/memory/ChatHistory.js +0 -143
- package/dist/memory/ChatHistory.js.map +0 -1
- package/dist/memory/KnowledgeBase.d.ts +0 -38
- package/dist/memory/KnowledgeBase.d.ts.map +0 -1
- package/dist/memory/KnowledgeBase.js +0 -139
- package/dist/memory/KnowledgeBase.js.map +0 -1
- package/dist/memory/Summarizer.d.ts +0 -12
- package/dist/memory/Summarizer.d.ts.map +0 -1
- package/dist/memory/Summarizer.js +0 -39
- package/dist/memory/Summarizer.js.map +0 -1
- package/dist/memory/UpstashKnowledgeBase.d.ts +0 -124
- package/dist/memory/UpstashKnowledgeBase.d.ts.map +0 -1
- package/dist/memory/UpstashKnowledgeBase.js +0 -152
- package/dist/memory/UpstashKnowledgeBase.js.map +0 -1
- package/dist/memory/historyUtils.d.ts +0 -58
- package/dist/memory/historyUtils.d.ts.map +0 -1
- package/dist/memory/historyUtils.js +0 -190
- package/dist/memory/historyUtils.js.map +0 -1
- package/dist/memory/index.d.ts +0 -7
- package/dist/memory/index.d.ts.map +0 -1
- package/dist/memory/index.js +0 -12
- package/dist/memory/index.js.map +0 -1
- package/dist/memory/ingestKnowledgeBase.d.ts +0 -17
- package/dist/memory/ingestKnowledgeBase.d.ts.map +0 -1
- package/dist/memory/ingestKnowledgeBase.js +0 -35
- package/dist/memory/ingestKnowledgeBase.js.map +0 -1
- package/dist/services/MoralisService.d.ts +0 -1328
- package/dist/services/MoralisService.d.ts.map +0 -1
- package/dist/services/MoralisService.js +0 -1239
- package/dist/services/MoralisService.js.map +0 -1
- package/dist/services/PantographService.d.ts +0 -98
- package/dist/services/PantographService.d.ts.map +0 -1
- package/dist/services/PantographService.js +0 -451
- package/dist/services/PantographService.js.map +0 -1
- package/dist/services/PoolService.d.ts +0 -238
- package/dist/services/PoolService.d.ts.map +0 -1
- package/dist/services/PoolService.js +0 -485
- package/dist/services/PoolService.js.map +0 -1
- package/dist/services/UniswapService.d.ts +0 -289
- package/dist/services/UniswapService.d.ts.map +0 -1
- package/dist/services/UniswapService.js +0 -585
- package/dist/services/UniswapService.js.map +0 -1
- package/dist/services/UniswapSubgraphService.d.ts +0 -144
- package/dist/services/UniswapSubgraphService.d.ts.map +0 -1
- package/dist/services/UniswapSubgraphService.js +0 -606
- package/dist/services/UniswapSubgraphService.js.map +0 -1
- package/dist/services/rpc.d.ts +0 -35
- package/dist/services/rpc.d.ts.map +0 -1
- package/dist/services/rpc.js +0 -110
- package/dist/services/rpc.js.map +0 -1
- package/dist/services/swap/BaseSwapService.d.ts +0 -17
- package/dist/services/swap/BaseSwapService.d.ts.map +0 -1
- package/dist/services/swap/BaseSwapService.js +0 -19
- package/dist/services/swap/BaseSwapService.js.map +0 -1
- package/dist/services/swap/DebridgeAdapter.d.ts +0 -37
- package/dist/services/swap/DebridgeAdapter.d.ts.map +0 -1
- package/dist/services/swap/DebridgeAdapter.js +0 -243
- package/dist/services/swap/DebridgeAdapter.js.map +0 -1
- package/dist/services/swap/RelayAdapter.d.ts +0 -19
- package/dist/services/swap/RelayAdapter.d.ts.map +0 -1
- package/dist/services/swap/RelayAdapter.js +0 -189
- package/dist/services/swap/RelayAdapter.js.map +0 -1
- package/dist/services/swap/SwapServiceFactory.d.ts +0 -25
- package/dist/services/swap/SwapServiceFactory.d.ts.map +0 -1
- package/dist/services/swap/SwapServiceFactory.js +0 -79
- package/dist/services/swap/SwapServiceFactory.js.map +0 -1
- package/dist/services/swap/addresses.d.ts +0 -3
- package/dist/services/swap/addresses.d.ts.map +0 -1
- package/dist/services/swap/addresses.js +0 -68
- package/dist/services/swap/addresses.js.map +0 -1
- package/dist/services/swap/affiliate.d.ts +0 -17
- package/dist/services/swap/affiliate.d.ts.map +0 -1
- package/dist/services/swap/affiliate.js +0 -126
- package/dist/services/swap/affiliate.js.map +0 -1
- package/dist/services/swap/config.d.ts +0 -9
- package/dist/services/swap/config.d.ts.map +0 -1
- package/dist/services/swap/config.js +0 -19
- package/dist/services/swap/config.js.map +0 -1
- package/dist/services/swap/index.d.ts +0 -10
- package/dist/services/swap/index.d.ts.map +0 -1
- package/dist/services/swap/index.js +0 -22
- package/dist/services/swap/index.js.map +0 -1
- package/dist/services/swap/types.d.ts +0 -89
- package/dist/services/swap/types.d.ts.map +0 -1
- package/dist/services/swap/types.js +0 -6
- package/dist/services/swap/types.js.map +0 -1
- package/dist/tools/BaseTool.d.ts +0 -15
- package/dist/tools/BaseTool.d.ts.map +0 -1
- package/dist/tools/BaseTool.js +0 -35
- package/dist/tools/BaseTool.js.map +0 -1
- package/dist/tools/ToolRegistry.d.ts +0 -27
- package/dist/tools/ToolRegistry.d.ts.map +0 -1
- package/dist/tools/ToolRegistry.js +0 -80
- package/dist/tools/ToolRegistry.js.map +0 -1
- package/dist/tools/builtin/ai/GeminiSearchAiTool.d.ts +0 -36
- package/dist/tools/builtin/ai/GeminiSearchAiTool.d.ts.map +0 -1
- package/dist/tools/builtin/ai/GeminiSearchAiTool.js +0 -91
- package/dist/tools/builtin/ai/GeminiSearchAiTool.js.map +0 -1
- package/dist/tools/builtin/ai/index.d.ts +0 -3
- package/dist/tools/builtin/ai/index.d.ts.map +0 -1
- package/dist/tools/builtin/ai/index.js +0 -6
- package/dist/tools/builtin/ai/index.js.map +0 -1
- package/dist/tools/builtin/index.d.ts +0 -8
- package/dist/tools/builtin/index.d.ts.map +0 -1
- package/dist/tools/builtin/index.js +0 -24
- package/dist/tools/builtin/index.js.map +0 -1
- package/dist/tools/builtin/nft/BaseNftMessageTool.d.ts +0 -35
- package/dist/tools/builtin/nft/BaseNftMessageTool.d.ts.map +0 -1
- package/dist/tools/builtin/nft/BaseNftMessageTool.js +0 -45
- package/dist/tools/builtin/nft/BaseNftMessageTool.js.map +0 -1
- package/dist/tools/builtin/nft/NFTContractInfoTool.d.ts +0 -15
- package/dist/tools/builtin/nft/NFTContractInfoTool.d.ts.map +0 -1
- package/dist/tools/builtin/nft/NFTContractInfoTool.js +0 -19
- package/dist/tools/builtin/nft/NFTContractInfoTool.js.map +0 -1
- package/dist/tools/builtin/nft/NFTMetadataTool.d.ts +0 -15
- package/dist/tools/builtin/nft/NFTMetadataTool.d.ts.map +0 -1
- package/dist/tools/builtin/nft/NFTMetadataTool.js +0 -20
- package/dist/tools/builtin/nft/NFTMetadataTool.js.map +0 -1
- package/dist/tools/builtin/nft/SendNftTool.d.ts +0 -14
- package/dist/tools/builtin/nft/SendNftTool.d.ts.map +0 -1
- package/dist/tools/builtin/nft/SendNftTool.js +0 -20
- package/dist/tools/builtin/nft/SendNftTool.js.map +0 -1
- package/dist/tools/builtin/nft/WalletNFTsTool.d.ts +0 -15
- package/dist/tools/builtin/nft/WalletNFTsTool.d.ts.map +0 -1
- package/dist/tools/builtin/nft/WalletNFTsTool.js +0 -20
- package/dist/tools/builtin/nft/WalletNFTsTool.js.map +0 -1
- package/dist/tools/builtin/nft/index.d.ts +0 -7
- package/dist/tools/builtin/nft/index.d.ts.map +0 -1
- package/dist/tools/builtin/nft/index.js +0 -10
- package/dist/tools/builtin/nft/index.js.map +0 -1
- package/dist/tools/builtin/pool/EstimatePoolYieldTool.d.ts +0 -124
- package/dist/tools/builtin/pool/EstimatePoolYieldTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool/EstimatePoolYieldTool.js +0 -236
- package/dist/tools/builtin/pool/EstimatePoolYieldTool.js.map +0 -1
- package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.d.ts +0 -128
- package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.js +0 -520
- package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.js.map +0 -1
- package/dist/tools/builtin/pool/PoolByAddressTool.d.ts +0 -127
- package/dist/tools/builtin/pool/PoolByAddressTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool/PoolByAddressTool.js +0 -238
- package/dist/tools/builtin/pool/PoolByAddressTool.js.map +0 -1
- package/dist/tools/builtin/pool/PoolDetailTool.d.ts +0 -127
- package/dist/tools/builtin/pool/PoolDetailTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool/PoolDetailTool.js +0 -273
- package/dist/tools/builtin/pool/PoolDetailTool.js.map +0 -1
- package/dist/tools/builtin/pool/PoolSearchTool.d.ts +0 -50
- package/dist/tools/builtin/pool/PoolSearchTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool/PoolSearchTool.js +0 -160
- package/dist/tools/builtin/pool/PoolSearchTool.js.map +0 -1
- package/dist/tools/builtin/pool/PreviewAddLiquidityTool.d.ts +0 -124
- package/dist/tools/builtin/pool/PreviewAddLiquidityTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool/PreviewAddLiquidityTool.js +0 -382
- package/dist/tools/builtin/pool/PreviewAddLiquidityTool.js.map +0 -1
- package/dist/tools/builtin/pool/TopPoolsTool.d.ts +0 -68
- package/dist/tools/builtin/pool/TopPoolsTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool/TopPoolsTool.js +0 -159
- package/dist/tools/builtin/pool/TopPoolsTool.js.map +0 -1
- package/dist/tools/builtin/pool/index.d.ts +0 -15
- package/dist/tools/builtin/pool/index.d.ts.map +0 -1
- package/dist/tools/builtin/pool/index.js +0 -18
- package/dist/tools/builtin/pool/index.js.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphCoinPoolPairsTool.d.ts +0 -54
- package/dist/tools/builtin/pool-subgraph/SubgraphCoinPoolPairsTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphCoinPoolPairsTool.js +0 -98
- package/dist/tools/builtin/pool-subgraph/SubgraphCoinPoolPairsTool.js.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolByAddressTool.d.ts +0 -63
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolByAddressTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolByAddressTool.js +0 -82
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolByAddressTool.js.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolByPositionIdTool.d.ts +0 -79
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolByPositionIdTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolByPositionIdTool.js +0 -97
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolByPositionIdTool.js.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolSearchTool.d.ts +0 -77
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolSearchTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolSearchTool.js +0 -190
- package/dist/tools/builtin/pool-subgraph/SubgraphPoolSearchTool.js.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphPositionDetailTool.d.ts +0 -107
- package/dist/tools/builtin/pool-subgraph/SubgraphPositionDetailTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphPositionDetailTool.js +0 -92
- package/dist/tools/builtin/pool-subgraph/SubgraphPositionDetailTool.js.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphTrendingPoolsTool.d.ts +0 -56
- package/dist/tools/builtin/pool-subgraph/SubgraphTrendingPoolsTool.d.ts.map +0 -1
- package/dist/tools/builtin/pool-subgraph/SubgraphTrendingPoolsTool.js +0 -94
- package/dist/tools/builtin/pool-subgraph/SubgraphTrendingPoolsTool.js.map +0 -1
- package/dist/tools/builtin/pool-subgraph/index.d.ts +0 -13
- package/dist/tools/builtin/pool-subgraph/index.d.ts.map +0 -1
- package/dist/tools/builtin/pool-subgraph/index.js +0 -18
- package/dist/tools/builtin/pool-subgraph/index.js.map +0 -1
- package/dist/tools/builtin/token/TokenAnalyticsTool.d.ts +0 -71
- package/dist/tools/builtin/token/TokenAnalyticsTool.d.ts.map +0 -1
- package/dist/tools/builtin/token/TokenAnalyticsTool.js +0 -147
- package/dist/tools/builtin/token/TokenAnalyticsTool.js.map +0 -1
- package/dist/tools/builtin/token/TokenHoldersTool.d.ts +0 -81
- package/dist/tools/builtin/token/TokenHoldersTool.d.ts.map +0 -1
- package/dist/tools/builtin/token/TokenHoldersTool.js +0 -139
- package/dist/tools/builtin/token/TokenHoldersTool.js.map +0 -1
- package/dist/tools/builtin/token/TokenInfoTool.d.ts +0 -40
- package/dist/tools/builtin/token/TokenInfoTool.d.ts.map +0 -1
- package/dist/tools/builtin/token/TokenInfoTool.js +0 -221
- package/dist/tools/builtin/token/TokenInfoTool.js.map +0 -1
- package/dist/tools/builtin/token/TokenScoreTool.d.ts +0 -63
- package/dist/tools/builtin/token/TokenScoreTool.d.ts.map +0 -1
- package/dist/tools/builtin/token/TokenScoreTool.js +0 -148
- package/dist/tools/builtin/token/TokenScoreTool.js.map +0 -1
- package/dist/tools/builtin/token/TopGainersTool.d.ts +0 -37
- package/dist/tools/builtin/token/TopGainersTool.d.ts.map +0 -1
- package/dist/tools/builtin/token/TopGainersTool.js +0 -100
- package/dist/tools/builtin/token/TopGainersTool.js.map +0 -1
- package/dist/tools/builtin/token/TopLosersTool.d.ts +0 -66
- package/dist/tools/builtin/token/TopLosersTool.d.ts.map +0 -1
- package/dist/tools/builtin/token/TopLosersTool.js +0 -129
- package/dist/tools/builtin/token/TopLosersTool.js.map +0 -1
- package/dist/tools/builtin/token/TrendingTokensTool.d.ts +0 -39
- package/dist/tools/builtin/token/TrendingTokensTool.d.ts.map +0 -1
- package/dist/tools/builtin/token/TrendingTokensTool.js +0 -83
- package/dist/tools/builtin/token/TrendingTokensTool.js.map +0 -1
- package/dist/tools/builtin/token/index.d.ts +0 -13
- package/dist/tools/builtin/token/index.d.ts.map +0 -1
- package/dist/tools/builtin/token/index.js +0 -16
- package/dist/tools/builtin/token/index.js.map +0 -1
- package/dist/tools/builtin/wallet/TransactionByHashTool.d.ts +0 -70
- package/dist/tools/builtin/wallet/TransactionByHashTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/TransactionByHashTool.js +0 -111
- package/dist/tools/builtin/wallet/TransactionByHashTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletApprovalsTool.d.ts +0 -107
- package/dist/tools/builtin/wallet/WalletApprovalsTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletApprovalsTool.js +0 -164
- package/dist/tools/builtin/wallet/WalletApprovalsTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletDefiPositionsTool.d.ts +0 -61
- package/dist/tools/builtin/wallet/WalletDefiPositionsTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletDefiPositionsTool.js +0 -100
- package/dist/tools/builtin/wallet/WalletDefiPositionsTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.d.ts +0 -68
- package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.js +0 -115
- package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletDefiSummaryTool.d.ts +0 -50
- package/dist/tools/builtin/wallet/WalletDefiSummaryTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletDefiSummaryTool.js +0 -80
- package/dist/tools/builtin/wallet/WalletDefiSummaryTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletHistoryTool.d.ts +0 -31
- package/dist/tools/builtin/wallet/WalletHistoryTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletHistoryTool.js +0 -154
- package/dist/tools/builtin/wallet/WalletHistoryTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletNetWorthTool.d.ts +0 -44
- package/dist/tools/builtin/wallet/WalletNetWorthTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletNetWorthTool.js +0 -122
- package/dist/tools/builtin/wallet/WalletNetWorthTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletNftTransfersTool.d.ts +0 -86
- package/dist/tools/builtin/wallet/WalletNftTransfersTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletNftTransfersTool.js +0 -265
- package/dist/tools/builtin/wallet/WalletNftTransfersTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletPnlSummaryTool.d.ts +0 -43
- package/dist/tools/builtin/wallet/WalletPnlSummaryTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletPnlSummaryTool.js +0 -89
- package/dist/tools/builtin/wallet/WalletPnlSummaryTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletPnlTool.d.ts +0 -43
- package/dist/tools/builtin/wallet/WalletPnlTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletPnlTool.js +0 -175
- package/dist/tools/builtin/wallet/WalletPnlTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletTokenBalancesTool.d.ts +0 -35
- package/dist/tools/builtin/wallet/WalletTokenBalancesTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletTokenBalancesTool.js +0 -68
- package/dist/tools/builtin/wallet/WalletTokenBalancesTool.js.map +0 -1
- package/dist/tools/builtin/wallet/WalletTokenTransfersTool.d.ts +0 -109
- package/dist/tools/builtin/wallet/WalletTokenTransfersTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/WalletTokenTransfersTool.js +0 -354
- package/dist/tools/builtin/wallet/WalletTokenTransfersTool.js.map +0 -1
- package/dist/tools/builtin/wallet/index.d.ts +0 -28
- package/dist/tools/builtin/wallet/index.d.ts.map +0 -1
- package/dist/tools/builtin/wallet/index.js +0 -32
- package/dist/tools/builtin/wallet/index.js.map +0 -1
- package/dist/tools/builtin/wallet-action/ApproveTokenTool.d.ts +0 -25
- package/dist/tools/builtin/wallet-action/ApproveTokenTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/ApproveTokenTool.js +0 -98
- package/dist/tools/builtin/wallet-action/ApproveTokenTool.js.map +0 -1
- package/dist/tools/builtin/wallet-action/BaseWalletActionTool.d.ts +0 -211
- package/dist/tools/builtin/wallet-action/BaseWalletActionTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/BaseWalletActionTool.js +0 -499
- package/dist/tools/builtin/wallet-action/BaseWalletActionTool.js.map +0 -1
- package/dist/tools/builtin/wallet-action/BuyTokenTool.d.ts +0 -240
- package/dist/tools/builtin/wallet-action/BuyTokenTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/BuyTokenTool.js +0 -1257
- package/dist/tools/builtin/wallet-action/BuyTokenTool.js.map +0 -1
- package/dist/tools/builtin/wallet-action/SendNativeTool.d.ts +0 -41
- package/dist/tools/builtin/wallet-action/SendNativeTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/SendNativeTool.js +0 -127
- package/dist/tools/builtin/wallet-action/SendNativeTool.js.map +0 -1
- package/dist/tools/builtin/wallet-action/SendTokenTool.d.ts +0 -63
- package/dist/tools/builtin/wallet-action/SendTokenTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/SendTokenTool.js +0 -294
- package/dist/tools/builtin/wallet-action/SendTokenTool.js.map +0 -1
- package/dist/tools/builtin/wallet-action/SwapTokenTool.d.ts +0 -247
- package/dist/tools/builtin/wallet-action/SwapTokenTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/SwapTokenTool.js +0 -1258
- package/dist/tools/builtin/wallet-action/SwapTokenTool.js.map +0 -1
- package/dist/tools/builtin/wallet-action/UnwrapNativeTool.d.ts +0 -20
- package/dist/tools/builtin/wallet-action/UnwrapNativeTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/UnwrapNativeTool.js +0 -36
- package/dist/tools/builtin/wallet-action/UnwrapNativeTool.js.map +0 -1
- package/dist/tools/builtin/wallet-action/WrapNativeTool.d.ts +0 -23
- package/dist/tools/builtin/wallet-action/WrapNativeTool.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/WrapNativeTool.js +0 -54
- package/dist/tools/builtin/wallet-action/WrapNativeTool.js.map +0 -1
- package/dist/tools/builtin/wallet-action/amountSpec.d.ts +0 -62
- package/dist/tools/builtin/wallet-action/amountSpec.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/amountSpec.js +0 -93
- package/dist/tools/builtin/wallet-action/amountSpec.js.map +0 -1
- package/dist/tools/builtin/wallet-action/gasReserve.d.ts +0 -42
- package/dist/tools/builtin/wallet-action/gasReserve.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/gasReserve.js +0 -103
- package/dist/tools/builtin/wallet-action/gasReserve.js.map +0 -1
- package/dist/tools/builtin/wallet-action/index.d.ts +0 -9
- package/dist/tools/builtin/wallet-action/index.d.ts.map +0 -1
- package/dist/tools/builtin/wallet-action/index.js +0 -20
- package/dist/tools/builtin/wallet-action/index.js.map +0 -1
- package/dist/tools/chainResolver.d.ts +0 -98
- package/dist/tools/chainResolver.d.ts.map +0 -1
- package/dist/tools/chainResolver.js +0 -302
- package/dist/tools/chainResolver.js.map +0 -1
- package/dist/tools/index.d.ts +0 -4
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -23
- package/dist/tools/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -777
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -6
- package/dist/types/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,71 +1,878 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// ============================================================
|
|
3
|
-
// keyring-agent-core – public API
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
"use strict";var Cn=Object.defineProperty;var Fo=Object.getOwnPropertyDescriptor;var Wo=Object.getOwnPropertyNames;var Vo=Object.prototype.hasOwnProperty;var Ho=(l,e)=>{for(var t in e)Cn(l,t,{get:e[t],enumerable:!0})},Go=(l,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Wo(e))!Vo.call(l,r)&&r!==t&&Cn(l,r,{get:()=>e[r],enumerable:!(n=Fo(e,r))||n.enumerable});return l};var Ko=l=>Go(Cn({},"__esModule",{value:!0}),l);var Ks={};Ho(Ks,{AI_AGENT_TOOL_NAMES:()=>kn,AgentCore:()=>An,ApproveTokenTool:()=>_t,BaseNftMessageTool:()=>ee,BaseSwapService:()=>ve,BaseTool:()=>v,BaseWalletActionTool:()=>J,BuyTokenTool:()=>Rt,ChatHistory:()=>Nt,DEFAULT_PROVIDER:()=>Vt,DEFAULT_RPC_BY_CHAIN:()=>ne,DebridgeAdapter:()=>dt,EstimatePoolYieldTool:()=>ft,GeminiProvider:()=>le,GeminiSearchAiTool:()=>nt,HEX_TO_PANTOGRAPH:()=>ro,KnowledgeBase:()=>Dt,MoralisService:()=>E,NFTContractInfoTool:()=>et,NFTMetadataTool:()=>tt,NFT_AGENT_TOOL_NAMES:()=>wn,OpenAddLiquidityFormTool:()=>ht,POOL_AGENT_TOOL_NAMES:()=>Tn,PantographService:()=>Y,PoolByAddressTool:()=>at,PoolDetailTool:()=>rt,PoolSearchTool:()=>st,PoolService:()=>_e,PreviewAddLiquidityTool:()=>gt,RelayAdapter:()=>mt,Router:()=>Lt,SWAP_PROVIDER_CONFIG:()=>mn,SendNativeTool:()=>Pt,SendNftTool:()=>Je,SendTokenTool:()=>At,Subagent:()=>V,SubgraphCoinPoolPairsTool:()=>vt,SubgraphPoolByAddressTool:()=>wt,SubgraphPoolByPositionIdTool:()=>kt,SubgraphPoolSearchTool:()=>yt,SubgraphPositionDetailTool:()=>Tt,SubgraphTrendingPoolsTool:()=>bt,Summarizer:()=>It,SwapServiceFactory:()=>Ae,SwapTokenTool:()=>Et,Synthesizer:()=>Mt,TOKEN_AGENT_TOOL_NAMES:()=>bn,TRANSFER_TOPIC:()=>xo,TRENDING_DEFAULT_BY_HEX_CHAIN:()=>so,TokenAnalyticsTool:()=>Be,TokenHoldersTool:()=>Me,TokenInfoTool:()=>Le,TokenScoreTool:()=>Oe,ToolRegistry:()=>Ee,TopGainersTool:()=>qe,TopPoolsTool:()=>ot,TransactionByHashTool:()=>Xe,TrendingTokensTool:()=>$e,UNISWAP_CHAIN_SLUG:()=>Gt,UnwrapNativeTool:()=>Ut,UpstashKnowledgeBase:()=>Se,WALLET_ACTION_AGENT_TOOL_NAMES:()=>vn,WALLET_AGENT_TOOL_NAMES:()=>yn,WalletApprovalsTool:()=>je,WalletDefiPositionsTool:()=>ze,WalletDefiProtocolPositionsTool:()=>Qe,WalletDefiSummaryTool:()=>Ye,WalletHistoryTool:()=>We,WalletNFTsTool:()=>Ze,WalletNetWorthTool:()=>on,WalletNftTransfersTool:()=>He,WalletPnlSummaryTool:()=>Ge,WalletPnlTool:()=>Ke,WalletTokenBalancesTool:()=>Fe,WalletTokenTransfersTool:()=>Ve,WrapNativeTool:()=>Ct,ZERO_ADDRESS:()=>L,buildRangePresets:()=>Fn,buildUniswapPoolUrl:()=>se,clampTick:()=>he,createAiAgent:()=>Qt,createDefaultSubagents:()=>Sn,createNftAgent:()=>zt,createPoolAgent:()=>Xt,createTokenAgent:()=>Yt,createWalletActionAgent:()=>Jt,createWalletAgent:()=>jt,ethCallAt:()=>To,ethCallByChain:()=>qn,getAffiliateFee:()=>ct,getChainMeta:()=>z,getDefaultRpcUrl:()=>Jr,getGatewayAddress:()=>hn,getNativeTokenInfo:()=>pt,getPositionManagerAddress:()=>gn,getProviderByChain:()=>dn,ingestKnowledgeBase:()=>$o,priceToTick:()=>Pe,resolveRpcUrl:()=>ut,roundTickToSpacing:()=>pe,rpcCall:()=>fe,setRpcOverrides:()=>$n,swapServiceFactory:()=>ce,tickToPrice:()=>oe,toPantographChain:()=>qt});module.exports=Ko(Ks);var Hn={string:"STRING",number:"NUMBER",boolean:"BOOLEAN",object:"OBJECT",array:"ARRAY"},jo="https://nft-demo.keyring.app/api/gemini-stable",Yo=new Set([408,429,500,502,503,504]),Gn=3e4,Zt=l=>new Promise(e=>setTimeout(e,l));function en(l,e,t){if(t!=null&&t>0)return Math.min(t,Gn);let n=Math.min(e*2**(l-1),Gn);return n/2+Math.random()*(n/2)}function zo(l){if(!l)return null;let e=Number(l);if(Number.isFinite(e))return Math.max(0,e*1e3);let t=Date.parse(l);return Number.isNaN(t)?null:Math.max(0,t-Date.now())}var le=class{model;maxTokens;temperature;baseUrl;constructor(e){this.model=e.model??"gemini-2.5-flash-lite",this.maxTokens=e.maxTokens??4096,this.temperature=e.temperature??1,this.baseUrl=e.baseUrl??jo}async chat(e,t,n){let{systemInstruction:r,contents:o}=this.toContents(e),{maxRetries:s=5,retryDelayMs:a=1e3}=n??{},i=o.length>0?o:[{role:"user",parts:[{text:"(continue)"}]}];if(n?._debug){console.log("[GeminiProvider] turn sequence:");for(let p of i){let m=p.parts.map(h=>h.functionCall?`fc:${h.functionCall.name}`:h.functionResponse?`fr:${h.functionResponse.name}`:`text:${(h.text??"").slice(0,40)}`).join(", ");console.log(` ${p.role}: [${m}]`)}}let c={contents:i,generationConfig:{maxOutputTokens:this.maxTokens,temperature:this.temperature}};r&&(c.systemInstruction=r),t?.length?c.tools=[{functionDeclarations:this.toFunctionDeclarations(t)}]:n?.googleSearch&&(c.tools=[{googleSearch:{}}]);let u=`${this.baseUrl}/v1beta/models/${this.model}:generateContent`,d="Unknown error";for(let p=1;p<=s;p++){let m;try{m=await fetch(u,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)})}catch(b){if(d=b instanceof Error?b.message:String(b),p>=s)break;await Zt(en(p,a));continue}if(!m.ok){let b=await m.text().catch(()=>m.statusText);if(d=`Gemini proxy error ${m.status}: ${b}`,!Yo.has(m.status))throw new Error(d);if(p>=s)break;let T=zo(m.headers.get("retry-after"));await Zt(en(p,a,T));continue}let h;try{h=await m.json()}catch(b){if(d=`Gemini proxy returned invalid JSON: ${b instanceof Error?b.message:String(b)}`,p>=s)break;await Zt(en(p,a));continue}let f=h.candidates?.[0];if(!f)return{text:"",toolCalls:[]};let g="",y=[],k=f.content?.parts;if(!Array.isArray(k)||k.length===0){let b=f.finishReason;if(!(b==="SAFETY"||b==="RECITATION")&&p<s){d=`Gemini returned an empty turn (finishReason=${b??"none"})`,await Zt(en(p,a));continue}return{text:"",toolCalls:[]}}for(let b of k)b.text&&(g+=b.text),b.functionCall&&y.push({toolName:b.functionCall.name,args:b.functionCall.args??{},callId:b.functionCall.id??`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`});let w=h.usageMetadata?{promptTokens:h.usageMetadata.promptTokenCount??0,completionTokens:h.usageMetadata.candidatesTokenCount??0,totalTokens:h.usageMetadata.totalTokenCount??0}:void 0;return{text:g,toolCalls:y,usage:w}}throw new Error(d)}toContents(e){let t=[],n=[];for(let a of e){if(a.role==="system"){t.push(a.content);continue}if(a.role==="tool"){n.push({role:"function",parts:[{functionResponse:{name:a.toolName??"unknown",response:{result:a.content},...a.toolCallId?{id:a.toolCallId}:{}}}]});continue}if(a.role==="assistant"&&a.toolCalls?.length){n.push({role:"model",parts:a.toolCalls.map(i=>({functionCall:{name:i.toolName,args:i.args,...i.callId?{id:i.callId}:{}}}))});continue}n.push({role:a.role==="assistant"?"model":"user",parts:[{text:a.content}]})}let r=[];for(let a of n){let i=r[r.length-1],c=a.parts.some(d=>d.functionCall),u=i?.parts.some(d=>d.functionCall);i&&i.role===a.role&&!c&&!u?i.parts.push(...a.parts):r.push({...a,parts:[...a.parts]})}let o=[];for(let a=0;a<r.length;a++){let i=r[a],c=o[o.length-1];i.role==="function"&&!c?.parts.some(d=>d.functionCall)||i.role==="model"&&i.parts.some(d=>d.functionCall)&&!(r[a+1]?.role==="function")||o.push(i)}for(;o.length>0&&o[0].role!=="user";)o.shift();return{systemInstruction:t.length?{role:"user",parts:[{text:t.join(`
|
|
2
|
+
`)}]}:null,contents:o}}toFunctionDeclarations(e){return e.map(t=>{let n={},r=[];for(let o of t.parameters){let s={type:Hn[o.type]??"STRING",description:o.description};o.type==="array"&&(s.items={type:Hn[o.items?.type??"string"]??"STRING"}),n[o.name]=s,o.required&&r.push(o.name)}return{name:t.name,description:t.description,parameters:{type:"OBJECT",properties:n,required:r}}})}};var Ee=class{tools=new Map;register(e){if(this.tools.has(e.name))throw new Error(`Tool "${e.name}" is already registered`);this.tools.set(e.name,e)}unregister(e){return this.tools.delete(e)}get(e){return this.tools.get(e)}has(e){return this.tools.has(e)}getDefinitions(){return Array.from(this.tools.values()).map(e=>({name:e.name,description:e.description,parameters:e.parameters,category:e.category,kind:e.kind}))}getDefinitionsByCategory(e){return this.getDefinitions().filter(t=>t.category===e)}async execute(e,t,n){let r=this.tools.get(e);if(!r)return{toolName:e,callId:`err_${Date.now()}`,success:!1,error:`Tool "${e}" not found in registry`,duration:0};let o=Date.now();try{return{...await r.execute(t,n),duration:Date.now()-o}}catch(s){return{toolName:e,callId:`err_${Date.now()}`,success:!1,error:s instanceof Error?s.message:String(s),duration:Date.now()-o}}}get size(){return this.tools.size}listNames(){return Array.from(this.tools.keys())}};var v=class{category;async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t);return{toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r}}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}};var Qo="0x2105",Xo=["0x1","0xa","0x38","0x89","0x2105","0xa4b1","0xa86a","0xe708"],Jo=new Set(Xo),tn="Ethereum (0x1), Optimism (0xa), BSC (0x38), Polygon (0x89), Base (0x2105), Arbitrum (0xa4b1), Avalanche (0xa86a), Linea (0xe708)",Zo={"0x1":"0x1",1:"0x1",eth:"0x1",ether:"0x1",ethereum:"0x1",mainnet:"0x1","0xa":"0xa",10:"0xa",op:"0xa",optimism:"0xa","0x38":"0x38",56:"0x38",bsc:"0x38",bnb:"0x38",binance:"0x38","0x89":"0x89",137:"0x89",matic:"0x89",polygon:"0x89","0x2105":"0x2105",8453:"0x2105",base:"0x2105","0xa4b1":"0xa4b1",42161:"0xa4b1",arb:"0xa4b1",arbitrum:"0xa4b1","0xa86a":"0xa86a",43114:"0xa86a",avax:"0xa86a",avalanche:"0xa86a","0xe708":"0xe708",59144:"0xe708",linea:"0xe708"};function me(l){if(typeof l!="string")return null;let e=l.trim().toLowerCase();if(!e)return null;let t=Zo[e];if(t)return t;let n=null;return/^0x[0-9a-f]+$/.test(e)?n=e:/^[0-9]+$/.test(e)&&(n="0x"+Number(e).toString(16)),n&&Jo.has(n)?n:null}function jn(l){return me(l)!==null}var Ne=class extends Error{constructor(t){super(`Chain "${t}" is not supported. Supported chains: ${tn}.`);this.requested=t;this.name="UnsupportedChainError"}requested;code="unsupported_chain"},Bt=l=>typeof l=="string"&&l.trim()!=="";function R(l,e,t=Qo){if(Bt(l)){let n=me(l);if(n)return n;throw new Ne(l.trim())}if(Bt(e?.chain)){let n=me(e.chain);if(n)return n;throw new Ne(e.chain.trim())}return t}function Yn(l,e){if(Bt(l)){let t=me(l);if(t)return t;throw new Ne(l.trim())}if(Bt(e?.chain)){let t=me(e.chain);if(t)return t;throw new Ne(e.chain.trim())}return null}function nn(l,e){return me(l)??me(e?.chain)??void 0}var Kn={"0x1":"Ethereum","0xa":"Optimism","0x38":"BSC","0x89":"Polygon","0x2105":"Base","0xa4b1":"Arbitrum","0xa86a":"Avalanche","0xe708":"Linea"};var er={bnb:"0x38",matic:"0x89",pol:"0x89",avax:"0xa86a"};function zn(l){if(typeof l!="string")return null;let e=l.trim().toLowerCase();return e?er[e]??null:null}function Ie(l,e){if(!Bt(l))return null;let t=me(l),n=me(e?.chain);return!t||!n||t===n?null:{requested:t,connected:n,requestedLabel:Kn[t]??t,connectedLabel:Kn[n]??n}}var Qn=require("js-sha3");function D(l){return typeof l=="string"&&/^0x[0-9a-fA-F]{40}$/.test(l)}function tr(l){return new Uint8Array(Qn.keccak256.arrayBuffer(l))}function Ot(l){let e=l.startsWith("0x")?l.slice(2):l,t=e.length%2?"0"+e:e,n=new Uint8Array(t.length/2);for(let r=0;r<n.length;r++)n[r]=parseInt(t.slice(r*2,r*2+2),16);return n}function Xn(l){return"0x"+Array.from(l).map(e=>e.toString(16).padStart(2,"0")).join("")}function Jn(l){return new TextEncoder().encode(l)}function xe(...l){let e=l.reduce((r,o)=>r+o.length,0),t=new Uint8Array(e),n=0;for(let r of l)t.set(r,n),n+=r.length;return t}function nr(l,e=32){let t=new Uint8Array(e);return t.set(l,e-l.length),t}function or(l,e=32){let t=new Uint8Array(e);return t.set(l),t}function Zn(l){let e=((l%(1n<<256n)+(1n<<256n))%(1n<<256n)).toString(16).padStart(64,"0");return Ot(e)}function $t(l){return Zn(BigInt(l))}function eo(l){return l==="uint"?"uint256":l==="int"?"int256":l.match(/^uint$/)?"uint256":l.match(/^int$/)?"int256":l.replace(/^uint(\[|$)/,"uint256$1").replace(/^int(\[|$)/,"int256$1")}function to(l){let e=eo(l.type);if(e==="tuple"||e.startsWith("tuple[")){let t=(l.components??[]).map(to).join(","),n=e.startsWith("tuple[")?e.slice(5):"";return`(${t})${n}`}return e}function rr(l){let e=(l.inputs??[]).map(to).join(",");return`${l.name??""}(${e})`}function Un(l,e){let t=ir(l.type);if(t){let[r,o]=t;return sr({...l,type:o},e,r)}if(l.type==="tuple")return ar(l,e);if(l.type==="address"){let r=String(e),o=r.startsWith("0x")?r.slice(2):r;return{dynamic:!1,encoded:nr(Ot(o.toLowerCase()))}}if(l.type==="bool"){let r=new Uint8Array(32);return r[31]=e?1:0,{dynamic:!1,encoded:r}}let n=eo(l.type);if(/^uint\d*$/.test(n))return{dynamic:!1,encoded:$t(BigInt(e))};if(/^int\d*$/.test(n)){let r=BigInt(e);return r<0n&&(r=r+(1n<<256n)),{dynamic:!1,encoded:Zn(r)}}if(/^bytes(\d+)$/.test(n)){let r=parseInt(n.slice(5),10),o=typeof e=="string"?Ot(e):e;if(o.length!==r)throw new Error(`bytes${r} expects exactly ${r} bytes, got ${o.length}`);return{dynamic:!1,encoded:or(o)}}if(n==="bytes"){let r=typeof e=="string"?Ot(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:xe($t(r.length),s)}}if(n==="string"){let r=typeof e=="string"?Jn(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:xe($t(r.length),s)}}throw new Error(`Unsupported ABI type: ${l.type}`)}function Rn(l){let e=0;for(let{dynamic:o,encoded:s}of l)e+=o?32:s.length;let t=[],n=[],r=0;for(let{dynamic:o,encoded:s}of l)o?(t.push($t(e+r)),n.push(s),r+=s.length):t.push(s);return xe(...t,...n)}function sr(l,e,t){let n=t===null;if(!n&&e.length!==t)throw new Error(`Array length mismatch: expected ${t}, got ${e.length}`);let r=!1,o=[];for(let s of e){let a=Un(l,s);a.dynamic&&(r=!0),o.push(a)}if(n||r){let s=Rn(o);if(n){let a=$t(o.length);return{dynamic:!0,encoded:o.length>0?xe(a,s):a}}return{dynamic:!0,encoded:s}}return{dynamic:!1,encoded:xe(...o.map(s=>s.encoded))}}function ar(l,e){let t=l.components??[],n=!1,r=[];for(let o=0;o<t.length;o++){let s=t[o],a=Array.isArray(e)?e[o]:e[s.name??""],i=Un(s,a);i.dynamic&&(n=!0),r.push(i)}return{dynamic:n,encoded:n?Rn(r):xe(...r.map(o=>o.encoded))}}function ir(l){let e=l.match(/^(.*)\[(\d+)?\]$/);return e?[e[2]?Number(e[2]):null,e[1]]:void 0}function lr(l,e){if(l.length!==e.length)throw new Error(`Expected ${l.length} values, got ${e.length}`);if(l.length===0)return"0x";let t=l.map((r,o)=>Un(r,e[o])),n=Rn(t);return Xn(n)}function De({abi:l,functionName:e,args:t=[]}){let n=l.find(i=>(i.type==="function"||i.type===void 0)&&i.name===e);if(!n)throw new Error(`Function "${e}" not found in ABI`);let r=rr(n),o=tr(Jn(r)).slice(0,4),s=n.inputs??[],a=s.length===0||t.length===0?new Uint8Array(0):Ot(lr(s,t).slice(2));return Xn(xe(o,a))}var cr="https://wallet-api.pantograph.app",ur="0x0000000000000000000000000000000000000000",ro={"0x1":"ether","0xa":"optimism","0x38":"bsc","0x89":"matic","0x2105":"base","0xa4b1":"arbitrum","0xa86a":"avax","0xe708":"linea"},so={"0x1":["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"],"0xa":["0x4200000000000000000000000000000000000006","0x68f180fcce6836688e9084f035309e29bf0a2095","0xc47da4cb96ce65a96844a01bfae509f9d5454534"],"0x38":["0x2170ed0880ac9a755fd29b2688956bd959f933f8","0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c"],"0x89":["0x7ceb23fd6bc0add59e62ac25578270cff1b9f619","0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6"],"0x2105":["0x4200000000000000000000000000000000000006","0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf","0xc47da4cb96ce65a96844a01bfae509f9d5454534"],"0xa4b1":["0x82af49447d8a07e3bd95bd0d56f35241523fbab1","0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f"],"0xa86a":["0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab","0x0555e30da8f98308edb960aa94c0db47230d2b9c"],"0xe708":["0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f","0x3aab2285ddcddad8edf438c1bab47e1a9d05a9b4"]};function qt(l){return ro[l.toLowerCase()]||l}function no(l){let e=[];for(let[t,n]of Object.entries(l))if(n!=null)if(Array.isArray(n))for(let r of n)e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(r))}`);else e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(n))}`);return e.join("&")}function En(l){let e=l,t=e.decimals,n=typeof t=="number"?t:typeof t=="string"?parseInt(t,10):NaN,r=e.price,o=r!=null?parseFloat(String(r)):NaN;return{...e,token_address:e.token_address??e.address??"",decimals:Number.isFinite(n)?n:0,...Number.isFinite(o)?{usd_price:o}:{}}}function j(l){if(l==null)return null;let e=typeof l=="number"?l:parseFloat(String(l));return Number.isFinite(e)?e:null}function oo(l,e){let t=l.decimals,n=typeof t=="number"?t:typeof t=="string"?parseInt(t,10):null,r=j(l.price_change_percentage_24h)??j(l.priceChange24h)??j(l.usd_price_24hr_percent_change)??j(l.price_change_24h)??j(l.priceChange);return{chainId:e,tokenAddress:l.address??l.token_address??"",name:l.name??null,symbol:l.symbol??null,decimals:n!=null&&Number.isFinite(n)?n:null,logo:l.logoURI??l.icon_image??l.logo??null,usdPrice:j(l.price)??j(l.usd_price),marketCap:j(l.market_cap)??j(l.marketCap),totalVolume:{"24h":j(l.total_volume)??j(l.volume24h)},pricePercentChange:{"24h":r}}}var Y=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??cr).replace(/\/+$/,"")}async enrichTokenPrices(e,t){if(e.length!==0)try{let n=qt(t),r=e.map(u=>u.token_address).join(","),o=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(r)}`,s=await fetch(o);if(!s.ok)return;let a=await s.json(),i=Array.isArray(a)?a:Array.isArray(a.data)?a.data:[],c=new Map;for(let u of i)u.address&&c.set(u.address.toLowerCase(),{price:u.price!=null?parseFloat(String(u.price)):NaN,icon_image:u.icon_image});for(let u=0;u<e.length;u++){let d=c.get(e[u].token_address.toLowerCase());d&&(d.icon_image&&(e[u].logo=d.icon_image,e[u].thumbnail=d.icon_image),isNaN(d.price)||(e[u].usd_price=d.price,e[u].usd_value=parseFloat(e[u].balance_formatted||"0")*d.price))}}catch{}}async getTokenMetadata(e,t){try{let n=qt(t),r=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(e)}`,o=await fetch(r);if(!o.ok)return null;let s=await o.json(),a=Array.isArray(s)?s:Array.isArray(s.data)?s.data:[],i=e.toLowerCase()===ur,c=a.find(u=>u.address?.toLowerCase()===e.toLowerCase()||i&&u.address===""||u.token_address?.toLowerCase()===e.toLowerCase()||i&&u.token_address==="");return c?En(c):null}catch{return null}}async getTokensMetadata(e,t){let n={};if(e.length===0)return n;try{let r=qt(t),o=`${this.baseUrl}/keyrings/tokens/${r}/v2?addresses=${encodeURIComponent(e.join(","))}`,s=await fetch(o);if(!s.ok)return n;let a=await s.json(),i=Array.isArray(a)?a:Array.isArray(a.data)?a.data:[],c=new Set(e.map(u=>u.toLowerCase()));for(let u of i){let d=u.address?.toLowerCase()||u.token_address?.toLowerCase()||(u.address===""?"":null);d&&c.has(d)&&(n[d]=En(u))}}catch{}return n}async searchTokensByKey(e){let{key:t,chain:n}=e;if(!t)return{success:!1,error:"Search key is required"};let o=parseInt(n||"0x2105",16);if(isNaN(o))return{success:!1,error:`Invalid hex chain: ${n}`};try{let s=no({chainId:o,key:t}),a=`${this.baseUrl}/token-list?${s}`,i=await fetch(a);if(!i.ok)throw new Error(`HTTP ${i.status}`);let c=await i.json(),u=[];return c?.data?u=Array.isArray(c.data)?c.data:Object.values(c.data).flat():typeof c=="object"&&!Array.isArray(c)&&(u=Object.values(c).flat()),{success:!0,data:u.map(En)}}catch(s){return{success:!1,error:s instanceof Error?s.message:"Unknown error"}}}async getTrendingTokens(e){let t=e?.chain||"0x2105",n=5,r=e?.limit&&e.limit>0?Math.floor(e.limit):void 0;try{let o=so[t.toLowerCase()]??[],s=r!=null?r+o.length:n+o.length,[a,i]=await Promise.allSettled([this.fetchTopGainers(t,s),o.length>0?this.getTokensMetadata(o,t):Promise.resolve({})]),c=a.status==="fulfilled"?a.value:[],u=i.status==="fulfilled"?o.map(f=>i.value[f.toLowerCase()]).filter(f=>f!=null).map(f=>oo(f,t)):[],d=new Set,p=[];for(let f of u){let g=f.tokenAddress?.toLowerCase();!g||d.has(g)||(d.add(g),p.push(f))}let m=[];for(let f of c){let g=f.tokenAddress?.toLowerCase();!g||d.has(g)||(d.add(g),m.push(f))}let h=r!=null?[...p,...m].slice(0,r):[...p,...m.slice(0,n)];return await this.enrichTrendingMarketData(h,t),{success:!0,data:h}}catch(o){return{success:!1,error:o instanceof Error?o.message:"Unknown error"}}}async enrichTrendingMarketData(e,t){let n=e.filter(o=>o.tokenAddress&&(o.marketCap==null||o.totalVolume?.["24h"]==null));if(n.length===0)return;let r=await this.getTokensMetadata(n.map(o=>o.tokenAddress),t);for(let o of n){let s=r[o.tokenAddress.toLowerCase()];s&&(o.marketCap==null&&(o.marketCap=j(s.market_cap)??j(s.marketCap)),o.totalVolume?.["24h"]==null&&(o.totalVolume={"24h":j(s.total_volume)??j(s.volume24h)}))}}async fetchTopGainers(e,t,n="24h"){let r=qt(e),o=no({duration:n,limit:t}),s=`${this.baseUrl}/token-list/top-gainers/${r}${o?`?${o}`:""}`,a=await fetch(s);if(!a.ok)throw new Error(`HTTP ${a.status}`);let i=await a.json();return(Array.isArray(i)?i:Array.isArray(i.data)?i.data:[]).map(u=>oo(u,e))}async getTopGainers(e){let t=e?.chain||"0x2105",n=e?.limit&&e.limit>0?Math.floor(e.limit):void 0,r=e?.duration||"24h";try{let o=await this.fetchTopGainers(t,n,r);return await this.enrichTrendingMarketData(o,t),{success:!0,data:o}}catch(o){return{success:!1,error:o instanceof Error?o.message:"Unknown error"}}}};var dr="https://nft.keyring.app",mr="https://nft-demo.keyring.app",pr=.01,U=5,B=1e3,hr="0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",gr="0x0000000000000000000000000000000000000000";function F(l){return l||"0x2105"}function O(l){let e=[];for(let[t,n]of Object.entries(l))if(n!=null)if(Array.isArray(n))for(let r of n)e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(r))}`);else e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(n))}`);return e.join("&")}function $(l){return new Promise(e=>setTimeout(e,l))}function fr(l){if(Array.isArray(l))return l;if(l&&typeof l=="object"){let e=l;return Array.isArray(e.result)?e.result.map(t=>({protocol_id:t.protocolId,protocol_name:t.protocolName,protocol_url:t.protocolUrl??"",protocol_logo:t.protocolLogo??"",position:{label:t.position.label,address:t.position.address,balance_usd:t.position.balanceUsd??0,total_unclaimed_usd_value:t.position.unclaimedUsd??0,position_details:t.position.details,tokens:t.position.tokens.map(n=>({token_type:n.tokenType,address:n.address,contract_address:n.address,name:n.name??"",symbol:n.symbol??"",decimals:n.decimals??18,logo:n.logo,balance:n.balance??"0",balance_formatted:n.balanceFormatted??"0",usd_price:n.usdPrice,usd_value:n.usdValue}))}})):Object.keys(e).filter(t=>/^\d+$/.test(t)).sort((t,n)=>Number(t)-Number(n)).map(t=>e[t]).filter(t=>!!t&&typeof t=="object")}return[]}function Nn(l){if(Array.isArray(l))return l;if(l&&typeof l=="object"){let e=l;return Array.isArray(e.result)?e.result:Object.keys(e).filter(t=>/^\d+$/.test(t)).sort((t,n)=>Number(t)-Number(n)).map(t=>e[t]).filter(t=>!!t&&typeof t=="object")}return[]}var E=class{baseUrl;v1BaseUrl;pantograph;constructor(e){this.baseUrl=(e?.baseUrl??dr).replace(/\/+$/,""),this.v1BaseUrl=(e?.v1BaseUrl??mr).replace(/\/+$/,""),this.pantograph=new Y({baseUrl:e?.pantographUrl})}async getWalletTokenBalances(e){let{address:t,chain:n,tokenAddresses:r,excludeSpam:o=!1,excludeUnverifiedContracts:s=!1}=e;if(!t)return{success:!1,error:"Address is required"};let a=F(n),i="Unknown error";for(let c=1;c<=U;c++)try{let u={chain:a,exclude_spam:o,exclude_unverified_contracts:s,limit:100};r&&(u.token_addresses=r);let d=[],p=null;do{p&&(u.cursor=p);let h=O(u),f=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/tokens?${h}`,g=await fetch(f);if(!g.ok)throw new Error(`HTTP ${g.status}`);let y=await g.json(),k=y?.data??y;k?.result?.length>0&&d.push(...k.result),p=k?.cursor??null}while(p);let m={result:d};return m?.result?.length>0&&(m.result=m.result.filter(h=>parseFloat(h.balance||"0")>0),m.result=m.result.map(h=>({...h,token_address:h.token_address.toLowerCase()===hr?gr:h.token_address})),await this.enrichTokenPrices(m.result,a),m.result=m.result.filter(h=>h.usd_value==null||h.usd_value>=pr)),{success:!0,data:m}}catch(u){i=u instanceof Error?u.message:"Unknown error",c<U&&await $(B)}return{success:!1,error:i}}async getTokenMetadata(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token address is required"};let r=F(n),o="Unknown error",s=await this.pantograph.getTokenMetadata(t,r);if(s)return{success:!0,data:s};for(let a=1;a<=U;a++)try{let[i,c]=await Promise.allSettled([fetch(`${this.baseUrl}/api/moralis/proxy/erc20/metadata?${O({chain:r,addresses:[t]})}`),fetch(`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/price?${O({chain:r,include:"percent_change"})}`)]),u=null;if(i.status==="fulfilled"&&i.value.ok){let p=await i.value.json();u=(Array.isArray(p)?p:Array.isArray(p?.data)?p.data:[])[0]??null}if(!u){o="Token metadata not found",a<U&&await $(B);continue}let d=null;if(c.status==="fulfilled"&&c.value.ok){let p=await c.value.json();d=p?.data??p}return{success:!0,data:{...u,...d?.usd_price?{usd_price:d.usd_price}:{},...d?.usd_price_change_percentage_24h?{usd_price_change_percentage_24h:d.usd_price_change_percentage_24h}:{}}}}catch(i){o=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}return{success:!1,error:o}}async getWalletNFTs(e){let{address:t,chain:n,limit:r=10,excludeSpam:o=!0,cursor:s,tokenAddresses:a,includePrices:i,format:c="decimal"}=e;if(!t)return{success:!1,error:"Address is required"};let u=F(n),d="Unknown error";for(let p=1;p<=U;p++)try{let m={chain:u,format:c,limit:r,exclude_spam:o,media_items:!0,normalizeMetadata:!0};s&&(m.cursor=s),i&&(m.include_prices=!0),a&&a.length>0&&(m.token_addresses=a);let h=O(m),f=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft?${h}`,g=await fetch(f);if(!g.ok)throw new Error(`HTTP ${g.status}`);let y=await g.json();return{success:!0,data:y?.data??y}}catch(m){d=m instanceof Error?m.message:"Unknown error",p<U&&await $(B)}return{success:!1,error:d}}async getNftMetadata(e){let{address:t,tokenId:n,chain:r,format:o="decimal",normalizeMetadata:s=!0,mediaItems:a=!0,include:i}=e;if(!t)return{success:!1,error:"NFT contract address is required"};if(n==null||n==="")return{success:!1,error:"Token ID is required"};if(!D(t))return{success:!1,error:"NFT metadata lookup by token ID is only supported on EVM chains"};let c=F(r),u="Unknown error";for(let d=1;d<=U;d++)try{let p={chain:c,format:o,normalizeMetadata:s,media_items:a};i&&(p.include=i);let m=O(p),h=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/${encodeURIComponent(n)}?${m}`,f=await fetch(h);if(!f.ok)throw new Error(`HTTP ${f.status}`);let g=await f.json();return{success:!0,data:g?.data??g}}catch(p){u=p instanceof Error?p.message:"Unknown error",d<U&&await $(B)}return{success:!1,error:u}}async getNftContractMetadata(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Contract address is required"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/metadata?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}return{success:!1,error:o}}async getWalletHistory(e){let{address:t,chain:n,limit:r=25,fromDate:o,toDate:s,fromBlock:a,toBlock:i,cursor:c,order:u="DESC",includeInternalTransactions:d,nftMetadata:p}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet history is only supported for EVM addresses"};let m=F(n),h=Math.max(1,Math.min(100,Math.floor(r))),f="Unknown error";for(let g=1;g<=U;g++)try{let y={chain:m,limit:h,order:u};o&&(y.from_date=o),s&&(y.to_date=s),a!=null&&(y.from_block=a),i!=null&&(y.to_block=i),c&&(y.cursor=c),d!=null&&(y.include_internal_transactions=d),p!=null&&(y.nft_metadata=p);let k=O(y),w=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/history?${k}`,b=await fetch(w);if(!b.ok)throw new Error(`HTTP ${b.status}`);let T=await b.json();return{success:!0,data:T?.data??T}}catch(y){f=y instanceof Error?y.message:"Unknown error",g<U&&await $(B)}return{success:!1,error:f}}async getWalletTokenTransfers(e){let{address:t,chain:n,contractAddresses:r,limit:o=25,fromDate:s,toDate:a,fromBlock:i,toBlock:c,cursor:u,order:d="DESC"}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"ERC-20 transfers are only supported for EVM addresses"};let p=F(n),m=Math.max(1,Math.min(100,Math.floor(o))),h="Unknown error";for(let f=1;f<=U;f++)try{let g={chain:p,limit:m,order:d};s&&(g.from_date=s),a&&(g.to_date=a),i!=null&&(g.from_block=i),c!=null&&(g.to_block=c),u&&(g.cursor=u),r?.length&&(g.contract_addresses=r);let y=O(g),k=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/erc20/transfers?${y}`,w=await fetch(k);if(!w.ok)throw new Error(`HTTP ${w.status}`);let b=await w.json();return{success:!0,data:b?.data??b}}catch(g){h=g instanceof Error?g.message:"Unknown error",f<U&&await $(B)}return{success:!1,error:h}}async getWalletNftTransfers(e){let{address:t,chain:n,contractAddresses:r,limit:o=25,fromDate:s,toDate:a,fromBlock:i,toBlock:c,cursor:u,order:d="DESC",includePrices:p,format:m="decimal"}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"NFT transfers are only supported for EVM addresses"};let h=F(n),f=Math.max(1,Math.min(100,Math.floor(o))),g="Unknown error";for(let y=1;y<=U;y++)try{let k={chain:h,limit:f,order:d,format:m};s&&(k.from_date=s),a&&(k.to_date=a),i!=null&&(k.from_block=i),c!=null&&(k.to_block=c),u&&(k.cursor=u),r?.length&&(k.contract_addresses=r),p!=null&&(k.include_prices=p);let w=O(k),b=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft/transfers?${w}`,T=await fetch(b);if(!T.ok)throw new Error(`HTTP ${T.status}`);let P=await T.json();return{success:!0,data:P?.data??P}}catch(k){g=k instanceof Error?k.message:"Unknown error",y<U&&await $(B)}return{success:!1,error:g}}async getTokenHolders(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Contract address is required"};if(!D(t))return{success:!1,error:"Token holders is only supported for EVM contract addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/holders?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}return{success:!1,error:o}}async getWalletNetWorth(e){let{address:t,chains:n,excludeSpam:r,excludeUnverifiedContracts:o,maxTokenInactivity:s,minPairSideLiquidityUsd:a}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet net worth is only supported for EVM addresses"};let i="Unknown error";for(let c=1;c<=U;c++)try{let u={};n&&n.length>0&&(u.chains=n.map(y=>F(y))),r!=null&&(u.exclude_spam=r),o!=null&&(u.exclude_unverified_contracts=o),s!=null&&(u.max_token_inactivity=s),a!=null&&(u.min_pair_side_liquidity_usd=a);let d=O(u),p=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/net-worth${d?`?${d}`:""}`,m=await fetch(p);if(!m.ok)throw new Error(`HTTP ${m.status}`);let h=await m.json();return{success:!0,data:h?.data??h}}catch(u){i=u instanceof Error?u.message:"Unknown error",c<U&&await $(B)}return{success:!1,error:i}}async getWalletPnlSummary(e){let{address:t,chain:n,days:r}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet PnL summary is only supported for EVM addresses"};let o=F(n),s="Unknown error";for(let a=1;a<=U;a++)try{let i={chain:o};r&&(i.days=r);let c=O(i),u=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability/summary?${c}`,d=await fetch(u);if(!d.ok)throw new Error(`HTTP ${d.status}`);let p=await d.json();return{success:!0,data:p?.data??p}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}return{success:!1,error:s}}async getWalletPnl(e){let{address:t,chain:n,days:r,tokenAddresses:o}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet PnL is only supported for EVM addresses"};let s=F(n),a="Unknown error";for(let i=1;i<=U;i++)try{let c={chain:s};r&&(c.days=r),o&&o.length>0&&(c.token_addresses=o.slice(0,25));let u=O(c),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability?${u}`,p=await fetch(d);if(!p.ok)throw new Error(`HTTP ${p.status}`);let m=await p.json();return{success:!0,data:m?.data??m}}catch(c){a=c instanceof Error?c.message:"Unknown error",i<U&&await $(B)}return{success:!1,error:a}}async getTransactionByHash(e){let{transactionHash:t,chain:n}=e;if(!t)return{success:!1,error:"Transaction hash is required"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/transaction/${encodeURIComponent(t)}/verbose?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}return{success:!1,error:o}}async getWalletApprovals(e){let{address:t,chain:n,limit:r,cursor:o}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet approvals are only supported for EVM addresses"};let s=F(n),a="Unknown error";for(let i=1;i<=U;i++)try{let c={chain:s};r!=null&&(c.limit=r),o&&(c.cursor=o);let u=O(c),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/approvals?${u}`,p=await fetch(d);if(!p.ok)throw new Error(`HTTP ${p.status}`);let m=await p.json();return{success:!0,data:m?.data??m}}catch(c){a=c instanceof Error?c.message:"Unknown error",i<U&&await $(B)}return{success:!1,error:a}}async getWalletDefiSummary(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet DeFi summary is only supported for EVM addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/summary?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}return{success:!1,error:o}}async getWalletDefiPositions(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet DeFi positions are only supported for EVM addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/positions?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json(),d=u?.data??u;return{success:!0,data:fr(d)}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}return{success:!1,error:o}}async getWalletDefiProtocolPositions(e){let{address:t,protocol:n,chain:r}=e;if(!t)return{success:!1,error:"Address is required"};if(!n)return{success:!1,error:"Protocol identifier is required"};if(!D(t))return{success:!1,error:"Wallet DeFi protocol positions are only supported for EVM addresses"};let o=F(r),s="Unknown error";for(let a=1;a<=U;a++)try{let i=O({chains:o}),c=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/${encodeURIComponent(n)}/positions?${i}`,u=await fetch(c);if(!u.ok)throw new Error(`HTTP ${u.status}`);let m=(await u.json()).result;return{success:!0,data:{protocol_id:m.protocolId,protocol_name:m.protocolName,protocol_url:m.protocolUrl??"",protocol_logo:m.protocolLogo??"",total_usd_value:m.totalUsd??0,total_unclaimed_usd_value:m.totalUnclaimedUsd??null,positions:m.positions.map(f=>({label:f.label,address:f.address,balance_usd:f.balanceUsd??0,total_unclaimed_usd_value:f.unclaimedUsd??0,position_details:f.details,tokens:f.tokens.map(g=>({token_type:g.tokenType,address:g.address,contract_address:g.address,name:g.name??"",symbol:g.symbol??"",decimals:g.decimals??18,logo:g.logo,balance:g.balance??"0",balance_formatted:g.balanceFormatted??"0",usd_price:g.usdPrice,usd_value:g.usdValue}))}))}}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}return{success:!1,error:s}}async searchTokensByKey(e){return this.pantograph.searchTokensByKey(e)}async getTokenAnalytics(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token contract address is required"};if(!D(t))return{success:!1,error:"Token analytics is only supported for EVM contract addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/analytics?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}return{success:!1,error:o}}async getTokenScore(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token contract address is required"};if(!D(t))return{success:!1,error:"Token score is only supported for EVM contract addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/score?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}return{success:!1,error:o}}async getTrendingTokens(e){let t=e?.chain,n=e?.limit,r="Unknown error";for(let o=1;o<=U;o++)try{let s={};t&&(s.chain=F(t)),n!=null&&(s.limit=n);let a=O(s),i=`${this.baseUrl}/api/moralis/proxy/tokens/trending${a?`?${a}`:""}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json(),d=u?.data??u;return{success:!0,data:Nn(d)}}catch(s){r=s instanceof Error?s.message:"Unknown error",o<U&&await $(B)}return{success:!1,error:r}}async getTopGainers(e){let{chain:t,timeFrame:n,minMarketCap:r,securityScore:o}=e??{},s="Unknown error";for(let a=1;a<=U;a++)try{let i={};t&&(i.chain=F(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let c=O(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-gainers${c?`?${c}`:""}`,d=await fetch(u);if(!d.ok)throw new Error(`HTTP ${d.status}`);let p=await d.json(),m=p?.data??p;return{success:!0,data:Nn(m)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}return{success:!1,error:s}}async getTopLosers(e){let{chain:t,timeFrame:n,minMarketCap:r,securityScore:o}=e??{},s="Unknown error";for(let a=1;a<=U;a++)try{let i={};t&&(i.chain=F(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let c=O(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-losers${c?`?${c}`:""}`,d=await fetch(u);if(!d.ok)throw new Error(`HTTP ${d.status}`);let p=await d.json(),m=p?.data??p;return{success:!0,data:Nn(m)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}return{success:!1,error:s}}async enrichTokenPrices(e,t){return this.pantograph.enrichTokenPrices(e,t)}};var Le=class extends v{name="get-token-info";description='Get information about a specific cryptocurrency token: price, metadata, 24h change, market data, and user holdings. Use this when the user asks about: token price, token info, "what is X token", "tell me about X", "how much is X worth", or any token-specific question. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea';category="blockchain-data";parameters=[{name:"keyword",type:"string",description:'Token symbol, name, or contract address (e.g. "USDC", "Pepe", "ETH", "0x1234\u2026"). If a 0x address is provided, performs a precise contract lookup. Otherwise searches by name/symbol.',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"prompt",type:"string",description:`A rich, multi-angle research prompt sent to Gemini with Google Search grounding (real-time web access). The prompt must be self-contained \u2014 Gemini does NOT see the user's original message, only this prompt. ALWAYS include: the token symbol/name, the chain, and an instruction to use web search for up-to-date info. When the user asks an open/broad question (e.g. "show me X", "tell me about X", "what is X"), expand the prompt to cover ALL of these angles in one request: (1) what the token/project is and its core utility, (2) current USD price, 24h change, market cap, FDV, 24h volume, (3) recent news, announcements, or notable events in the last 7-30 days, (4) on-chain activity signals (holders, liquidity, unusual flows), (5) team/protocol updates or roadmap items, (6) key risks, red flags, or controversies, (7) sentiment and notable community discussion. When the user asks a narrow question (e.g. "price of X"), still include 2-3 supporting angles for context. Aim for 3-6 sentences. Be specific to the token. Do not use generic filler. Example for "show me JPYT": "Research the JPYT token on Ethereum using up-to-date web sources. Cover: what JPYT is and who issues it; current USD price, 24h change, market cap, FDV, and 24h volume; recent news or announcements in the last 30 days; on-chain signals such as holder count, liquidity, and unusual volume patterns; team or protocol updates; key risks or red flags; and overall sentiment."`,required:!0},{name:"buy_button_label",type:"string",description:`Label for the "Buy" suggestion button shown under the token info, IN THE USER'S CURRENT LANGUAGE. Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "Buy {token}". Vietnamese example: "Mua {token}". Japanese example: "{token} \u3092\u8CB7\u3046". Always include {token}.`,required:!0},{name:"buy_prompt_template",type:"string",description:`A short "buy" command IN THE USER'S CURRENT LANGUAGE, submitted as the next user turn when the Buy button is clicked. Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "buy {token}". Vietnamese example: "mua {token}". Japanese example: "{token} \u3092\u8CB7\u3046". Always include {token}.`,required:!0}];service;llm;constructor(e){super(),this.service=new E(e),this.llm=new le({})}async run(e,t){let n=e.keyword||"",r=R(e.chain,t),o=e.prompt||"",s=e.buy_button_label||"",a=e.buy_prompt_template||"",i=t?.walletAddress||void 0;return n?await this.searchToken(n,r,o,s,a,i):{error:"Missing required parameter: keyword"}}async execute(e,t){let n=await super.execute(e,t),r=n.data;return n.success&&r&&Array.isArray(r.actionButtons)&&r.actionButtons.length>0&&(n.actionButtons=r.actionButtons),n}async searchToken(e,t,n,r,o,s){let a=[s?`User's connected wallet address: ${s}`:null,t?`Current chain (hex chain ID): ${t}, only consider tokens on this chain`:null,"Use this context to personalize your answer where relevant (e.g. whether the user holds the token, chain-specific data)."].filter(Boolean).join(`
|
|
3
|
+
`),[i,c,u]=await Promise.all([this.service.searchTokensByKey({key:e,chain:t}),s?this.findTokenInWallet(s,t,e):Promise.resolve(null),n?this.askGeminiWithSearch(n,a):Promise.resolve(null)]),d=i.success&&i.data?Array.isArray(i.data)?i.data:[]:[],p=e.toLowerCase(),m=b=>b.symbol?.toLowerCase()===p||b.token_address?.toLowerCase()===p,h=[...d].sort((b,T)=>(m(b)?0:1)-(m(T)?0:1)),f=null,g=h[0];if(g?.token_address){let b=await this.service.getTokenMetadata({address:g.token_address,chain:t});b.success&&b.data&&(f=b.data)}!f&&g&&(f={...g});let y=f?.symbol?.trim(),k=y?[{label:this.fillTemplate(r,y,"Buy {token}"),prompt:this.fillTemplate(o,y,"buy {token}")}]:[];return{_instructions:'Present the token info in a clear summary. Include: token name & symbol, contract address (shortened), current USD price, 24h price change % (use \u25B2 for positive, \u25BC for negative), market cap, fully diluted valuation, 24h trading volume, liquidity, and holder count (if available). If the user holds this token, prominently show their balance and USD value. If research is present, weave its substance naturally into your answer \u2014 fold the relevant points into the description, context, and any notable news or risks. Do NOT add a labelled "AI Analysis" section or any similar heading, and do NOT mention that the analysis came from AI; just write it as part of the token summary. Format numbers in a human-readable way (e.g. $1.23, +5.67%, $12.5M). If a field is null, omit it. A "Buy" button is shown below your reply automatically \u2014 do NOT mention it, list it, or repeat its text.',chain:t,userHoldsToken:!!c,walletBalance:c,token:f,...d.length>1?{searchResults:h.slice(0,10)}:{},research:u,...k.length>0?{actionButtons:k}:{}}}fillTemplate(e,t,n){return(e&&e.includes("{token}")?e:n).replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}async askGeminiWithSearch(e,t){let n=Date.now();try{return(await this.llm.chat([...t?[{role:"system",content:t,timestamp:n}]:[],{role:"user",content:e,timestamp:n}],void 0,{googleSearch:!0,maxRetries:1})).text?.trim()||null}catch{return null}}async findTokenInWallet(e,t,n){if(!n)return null;let r=await this.service.getWalletTokenBalances({address:e,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0});if(!r.success||!r.data?.result)return null;let o=n.toLowerCase(),s=r.data.result.find(a=>a.token_address.toLowerCase()===o||a.symbol?.toLowerCase()===o||a.name?.toLowerCase().includes(o));return s||null}};var Me=class extends v{name="get-token-holders";description='Get holder statistics and analytics for an ERC-20 token. Returns: total holder count, holder count changes over time (5min/1h/6h/24h/3d/7d/30d), how holders acquired the token (swap/transfer/airdrop), supply concentration (% held by top 10/25/50/100/250/500 holders), and holder size distribution (whales, sharks, dolphins, fish, octopus, crabs, shrimps). Use tokenSymbol (e.g. "USDC") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "How many holders does USDC have?" (tokenSymbol=USDC), "Is USDC gaining or losing holders?" (tokenSymbol=USDC \u2192 holderChange), "How many new holders did ETH get in the last 7 days?" (tokenSymbol=WETH), "How concentrated is USDC ownership?" (tokenSymbol=USDC \u2192 holderSupply), "What % of supply do the top 10 holders control?" (tokenSymbol=USDC), "How many whales hold USDT?" (tokenSymbol=USDT \u2192 holderDistribution), "Did most holders buy via swap or transfer?" (tokenSymbol=USDC \u2192 holdersByAcquisition). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. ';category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "USDC", "USDT", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=R(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "USDC") or contractAddress (0x\u2026).'};let a,i;if(o){let d=await this.resolveContractAddress(o,n,r);if(!d)return{error:`Cannot resolve token symbol "${o}" to a contract address on chain ${n??"default"}. Try passing contractAddress directly.`};s=d.address,a=d.symbol,i=d.name}let c=await this.service.getTokenHolders({address:s,chain:n});if(!c.success)return{error:c.error||"Failed to fetch token holder stats"};let u=c.data;return{token:{symbol:a??o??null,name:i??null,contractAddress:s,chain:n||"default"},totalHolders:u.totalHolders,holdersByAcquisition:u.holdersByAcquisition,holderChange:u.holderChange,holderSupply:u.holderSupply,holderDistribution:u.holderDistribution}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};var Be=class extends v{name="get-token-analytics";description=`Get detailed trading analytics for a single ERC-20 token, broken down across 5min / 1h / 6h / 24h windows. Returns: buy volume, sell volume, unique buyers, unique sellers, buy/sell transaction counts, unique wallets, price % change, current USD price, total liquidity and fully-diluted valuation (FDV). Use tokenSymbol (e.g. "PEPE") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "How is PEPE trading today?" (tokenSymbol=PEPE), "24h buy vs sell volume for USDC?" (tokenSymbol=USDC \u2192 totalBuyVolume vs totalSellVolume), "Is buying or selling pressure stronger for this token?", "How many unique wallets touched this token in the last hour?" (\u2192 uniqueWallets.1h), "What's the FDV and liquidity of X?" (\u2192 totalFullyDilutedValuation, totalLiquidity), "Price change over 6h?" (\u2192 pricePercentChange.6h). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "PEPE", "USDC", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=R(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "PEPE") or contractAddress (0x\u2026).'};let a,i;if(o){let d=await this.resolveContractAddress(o,n,r);if(!d)return{error:`Cannot resolve token symbol "${o}" to a contract address on chain ${n??"default"}. Try passing contractAddress directly.`};s=d.address,a=d.symbol,i=d.name}let c=await this.service.getTokenAnalytics({address:s,chain:n});if(!c.success)return{error:c.error||"Failed to fetch token analytics"};let u=c.data??{};return{token:{symbol:a??o??null,name:i??null,contractAddress:s,chain:n||"default"},usdPrice:u.usdPrice??null,totalLiquidity:u.totalLiquidity??null,totalFullyDilutedValuation:u.totalFullyDilutedValuation??null,totalBuyVolume:u.totalBuyVolume??null,totalSellVolume:u.totalSellVolume??null,totalBuyers:u.totalBuyers??null,totalSellers:u.totalSellers??null,totalBuys:u.totalBuys??null,totalSells:u.totalSells??null,uniqueWallets:u.uniqueWallets??null,pricePercentChange:u.pricePercentChange??null}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};var Oe=class extends v{name="get-token-score";description=`Get a composite 0\u2013100 health/safety score for a single ERC-20 token, plus the metrics behind it. Returns: overall score, last-updated timestamp, current USD price, paired liquidity, multi-timeframe volume buckets (10m / 30m / 1h / 4h / 12h / 1d / 7d / 30d), same buckets for transaction counts, and supply info (total supply + % held by top 10 holders). Use tokenSymbol (e.g. "PEPE") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "Is PEPE a safe token?" (tokenSymbol=PEPE \u2192 score), "What's the health/score of USDC?", "How concentrated is X's supply among whales?" (\u2192 metrics.supply.top10Percent), "How much volume did this token do over the last 7 days?" (\u2192 metrics.volumeUsd.7d), "Transaction count for this token in the last hour?" (\u2192 metrics.transactions.1h). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "PEPE", "USDC", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=R(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "PEPE") or contractAddress (0x\u2026).'};let a,i;if(o){let p=await this.resolveContractAddress(o,n,r);if(!p)return{error:`Cannot resolve token symbol "${o}" to a contract address on chain ${n??"default"}. Try passing contractAddress directly.`};s=p.address,a=p.symbol,i=p.name}let c=await this.service.getTokenScore({address:s,chain:n});if(!c.success)return{error:c.error||"Failed to fetch token score"};let u=c.data??{},d=u.metrics??{};return{token:{symbol:a??o??null,name:i??null,contractAddress:s,chain:n||"default"},score:u.score??null,updatedAt:u.updatedAt??null,metrics:{usdPrice:d.usdPrice??null,liquidityUsd:d.liquidityUsd??null,volumeUsd:d.volumeUsd??null,transactions:d.transactions??null,supply:d.supply??null}}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};var $e=class extends v{name="get-trending-tokens";description='List tokens trending on-chain right now, ranked by trading activity, volume, liquidity and holder growth. Returns price, market cap, liquidity, holder count, and multi-timeframe buckets (1h / 4h / 12h / 24h) of price change, volume, transaction counts, and unique buyers/sellers. Cross-chain by default \u2014 omit `chain` for a global ranking, or pass it to filter to one network. Use this tool for questions like: "What tokens are trending right now?", "Show me hot tokens", "What\'s popular on Base today?", "Which tokens have the highest 24h volume?", "Biggest movers in the last hour?". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea, and more.';category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Optional hex chain ID to scope results to a single network: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user names a chain. Omit to return cross-chain trending results.',required:!1},{name:"limit",type:"number",description:'Total number of trending tokens to return (chain default tokens + top gainers combined). Only set this when the user asks for a specific count (e.g. "show 20 trending tokens" \u2192 20). Omit otherwise \u2014 when omitted, returns all default tokens plus 5 top gainers.',required:!1}];service;constructor(e){super(),this.service=new Y({baseUrl:e?.pantographUrl})}async run(e,t){let n=nn(e.chain,t),r=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.floor(e.limit):void 0,o=await this.service.getTrendingTokens({chain:n,limit:r});if(!o.success)return{error:o.error||"Failed to fetch trending tokens"};let s=o.data??[];return{_instructions:`Start with a one-line header like "Here are some of the top trending tokens on <chain name> right now:". Then present the tokens as a numbered list. For EACH token render exactly these lines (omit any line whose value is null):
|
|
4
|
+
<rank>. <name> (<symbol>)
|
|
5
|
+
- Price: <usdPrice>
|
|
6
|
+
- Market Cap: <marketCap>
|
|
7
|
+
- 24h Price Change: <pricePercentChange.24h>
|
|
8
|
+
- 24h Volume: <totalVolume.24h>
|
|
9
|
+
Format numbers human-readably: price as $0.65 / $0.016 (more decimals for sub-cent), market cap & volume compact ($12.8M, $6,474), price change signed with one or two decimals (+3.56%, -1.6%). Reply in the user's language and use the human-readable chain name. Do NOT mention tool names, UI, or forms.`,chain:n??"all",count:s.length,tokens:s}}};var io={"1h":"1h","1d":"24h","1w":"7d","1M":"30d"},ao=Object.keys(io),qe=class extends v{name="get-top-gainers";description='List tokens with the highest USD price increase over a chosen timeframe (1h / 1d / 1w / 1M). Returns name & symbol, current USD price, market cap, 24h volume, and the price change % over the selected window. Scoped to a single chain \u2014 pass `chain` to pick the network, otherwise the connected chain (or Base) is used. Use this tool for questions like: "What are today\'s top gainers?", "Biggest pumps this week?", "Which tokens went up most in the last hour?", "Top gainers on Base". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. Defaults to the connected chain, or Base (0x2105).',required:!1},{name:"timeFrame",type:"string",description:`Window over which the price change is measured. Allowed values: "1h" (last hour), "1d" (last 24h), "1w" (last 7 days), "1M" (last 30 days). Pick the timeframe that matches the user's question \u2014 e.g. "today" \u2192 "1d", "this week" \u2192 "1w". Defaults to "1d".`,required:!1},{name:"limit",type:"number",description:'Number of top gainers to return. Only set this when the user asks for a specific count (e.g. "show 20 top gainers" \u2192 20). Omit otherwise.',required:!1}];service;constructor(e){super(),this.service=new Y({baseUrl:e?.pantographUrl})}async run(e,t){let n=nn(e.chain,t),r=typeof e.timeFrame=="string"?e.timeFrame.trim():void 0;if(r&&!ao.includes(r))return{error:`Invalid timeFrame "${r}". Allowed values: ${ao.join(", ")}.`};let o=r||void 0,s=o?io[o]:void 0,a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.floor(e.limit):void 0,i=await this.service.getTopGainers({chain:n,limit:a,duration:s});if(!i.success)return{error:i.error||"Failed to fetch top gainers"};let c=i.data??[];return{_instructions:`Start with a one-line header like "Here are the top gainers on <chain name> right now:". Then present the tokens as a numbered list. For EACH token render exactly these lines (omit any line whose value is null):
|
|
10
|
+
<rank>. <name> (<symbol>)
|
|
11
|
+
- Price: <usdPrice>
|
|
12
|
+
- Market Cap: <marketCap>
|
|
13
|
+
- Price Change: <pricePercentChange.24h> (use \u25B2 for positive, \u25BC for negative)
|
|
14
|
+
- 24h Volume: <totalVolume.24h>
|
|
15
|
+
Format numbers human-readably: price as $0.65 / $0.016 (more decimals for sub-cent), market cap & volume compact ($12.8M, $6,474), price change signed with one or two decimals (+5.67%, -1.6%). Reply in the user's language and use the human-readable chain name. Do NOT mention tool names, UI, or forms.`,chain:n??"all",timeFrame:o??null,count:c.length,tokens:c}}};var Fe=class extends v{name="get-wallet-token-balances";description='Get all ERC-20 token balances for a wallet address, including USD prices and 24h changes. Use this when the user asks about: "my tokens", "my balances", "what do I hold", "portfolio", "how much ETH/USDC/\u2026 do I have", or any wallet balance question. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. ';category="blockchain-data";parameters=[{name:"address",type:"string",description:"Wallet address to query (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:"No wallet address available. Please connect a wallet."};let o=await this.service.getWalletTokenBalances({address:n,chain:r,excludeSpam:!1,excludeUnverifiedContracts:!1});if(!o.success)return{error:o.error||"Failed to fetch token balances"};let s=o.data?.result||[];return{walletAddress:n,chain:r||"default",tokenCount:s.length,tokens:s,totalUsdValue:s.reduce((a,i)=>a+(i.usd_value||0),0)}}};var We=class extends v{name="get-wallet-history";description=`Get a wallet's decoded on-chain transaction history. Always fetches the latest 100 transactions. Each transaction includes: category, summary, from_address, to_address, and nested arrays: native_transfers (each has direction="send"|"receive", from_address, to_address, value_formatted, token_symbol), erc20_transfers (each has direction, token_symbol, value_formatted), nft_transfers. To find ETH sends/receives, inspect native_transfers[].direction \u2014 ETH moves appear across many categories (e.g. "token swap", "send", "receive"), not just category="send". Use this tool for: native coin transfers, swaps, airdrops, deposits, withdrawals, contract interactions, mixed history. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page. Do NOT use for: specific ERC-20 tokens \u2192 get-wallet-token-transfers; NFT transfers \u2192 get-wallet-nft-transfers; balances \u2192 get-wallet-token-balances.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"},{name:"includeInternalTransactions",type:"boolean",description:"Include decoded internal transactions on each item.",required:!1},{name:"nftMetadata",type:"boolean",description:"Include NFT normalized metadata in nft_transfers.",required:!1},{name:"limit",type:"number",description:"Number of transactions to return per page. Defaults to 10.",required:!1,default:10}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,s=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,a=typeof e.fromBlock=="number"?e.fromBlock:typeof e.fromBlock=="string"&&e.fromBlock?Number(e.fromBlock):void 0,i=typeof e.toBlock=="number"?e.toBlock:typeof e.toBlock=="string"&&e.toBlock?Number(e.toBlock):void 0,c=Number.isFinite(a)?a:void 0,u=Number.isFinite(i)?i:void 0,d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,m=(typeof e.order=="string"?e.order.toUpperCase():void 0)==="ASC"?"ASC":"DESC",h=typeof e.includeInternalTransactions=="boolean"?e.includeInternalTransactions:void 0,f=typeof e.nftMetadata=="boolean"?e.nftMetadata:void 0,g=typeof e.limit=="number"?e.limit:typeof e.limit=="string"?Number(e.limit):void 0,y=Number.isFinite(g)&&g>0?g:10,k=await this.service.getWalletHistory({address:n,chain:r,limit:y,fromDate:o,toDate:s,fromBlock:c,toBlock:u,cursor:d,order:m,includeInternalTransactions:h,nftMetadata:f});if(!k.success)return{error:k.error||"Failed to fetch wallet history"};let w=k.data?.result||[];return{walletAddress:n,chain:r||"default",pagination:{cursor:k.data?.cursor??null,hasMore:!!k.data?.cursor},transactionCount:w.length,transactions:w}}};function yr(l){if(l.value_decimal)return l.value_decimal;let e=parseInt(l.token_decimals??"18",10);if(isNaN(e)||e===0)return l.value;try{let t=BigInt(l.value),n=BigInt(10)**BigInt(e),r=t/n,s=(t%n).toString().padStart(e,"0").replace(/0+$/,"");return s?`${r}.${s}`:`${r}`}catch{return l.value}}var Ve=class extends v{name="get-wallet-token-transfers";description=`Get all ERC-20 token transfers for a wallet from the dedicated Moralis transfers endpoint. Each transfer includes direction (send/receive), human-readable amount, and the exact counterparty address with its label/entity. The response includes a "counterparties" section that aggregates unique sender/recipient addresses with totals. Use tokenSymbol (e.g. "USDC") to filter by token name \u2014 the tool automatically resolves the contract address by checking the wallet's token balances first, then falling back to token search. Use contractAddresses when you already have the exact contract address. Use this tool for questions like: "When did I last send USDC and to whom?" (tokenSymbol=USDC, direction=send, limit=1), "Who have I sent token 0x\u2026 to?" (contractAddresses=[0x\u2026], direction=send), "Who sent me USDT?" (tokenSymbol=USDT, direction=receive), "Show all USDC transfers" (tokenSymbol=USDC), "What tokens did I receive this month?" (fromDate=..., direction=receive). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page. IMPORTANT \u2014 this tool covers ERC-20 tokens ONLY. Do NOT use it for native coins (ETH, BNB, MATIC, AVAX, etc.) \u2014 native coin transfers are not ERC-20 and will not appear here. For native coin transfer history use get-wallet-history instead.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"tokenSymbol",type:"string",description:'Filter by token symbol (e.g. "USDC", "USDT", "WETH"). The tool resolves the contract address automatically: first checks wallet balances, then searches by symbol on the chain. Use this when the user mentions a token by name/symbol. Takes precedence over contractAddresses when both are given.',required:!1},{name:"contractAddresses",type:"array",description:"Filter by one or more ERC-20 token contract addresses (0x\u2026). Use when the exact contract address is already known. Ignored if tokenSymbol is provided.",required:!1,items:{type:"string"}},{name:"direction",type:"string",description:'Client-side direction filter: "send" (wallet is sender), "receive" (wallet is recipient), "all" (default).',required:!1,default:"all"},{name:"limit",type:"number",description:'Page size (1-100, default 25). Use limit=1 ONLY when the user asks for the single most recent transfer (e.g. "L\u1EA7n cu\u1ED1i c\xF9ng t\xF4i g\u1EEDi USDC l\xE0 khi n\xE0o?"). For "show recent history" or "list my transfers" questions use the default (25). Use 100 for bulk counterparty analysis.',required:!1,default:25},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):25,a=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,i=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,c=this.parseBlock(e.fromBlock),u=this.parseBlock(e.toBlock),d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,m=(typeof e.order=="string"?e.order.toUpperCase():"")==="ASC"?"ASC":"DESC",h=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",f=h==="send"?"send":h==="receive"?"receive":"all",g=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(S=>typeof S=="string"&&!!S).map(S=>S.trim()):void 0,k,w;if(g){let S=await this.resolveContractAddressBySymbol(g,n,r);S&&(y=[S.address],k=S.symbol,w=S.name)}let b=await this.service.getWalletTokenTransfers({address:n,chain:r,contractAddresses:y,limit:s,fromDate:a,toDate:i,fromBlock:c,toBlock:u,cursor:d,order:m});if(!b.success)return{error:b.error||"Failed to fetch token transfers"};let T=n.toLowerCase(),x=(b.data?.result||[]).map(S=>{let _=S.from_address.toLowerCase()===T,C=_?"send":"receive",q=_?S.to_address:S.from_address,K=_?S.to_address_label??void 0:S.from_address_label??void 0,H=_?S.to_address_entity??void 0:S.from_address_entity??void 0;return{...S,direction:C,amount_formatted:yr(S),counterparty:q,counterparty_label:K,counterparty_entity:H}}),A=f==="all"?x:x.filter(S=>S.direction===f);if(g&&!y){let S=g.toLowerCase();A=A.filter(_=>(_.token_symbol??"").toLowerCase()===S)}let I=this.buildCounterparties(A);return{walletAddress:n,chain:r||"default",resolvedToken:k?{symbol:k,name:w,contractAddress:y?.[0]}:null,filters:{tokenSymbol:g||null,contractAddresses:y||null,direction:f,limit:s,order:m,fromDate:a||null,toDate:i||null,fromBlock:c??null,toBlock:u??null},pagination:{cursor:b.data?.cursor??null,pageSize:b.data?.page_size??s,page:b.data?.page??null,hasMore:!!b.data?.cursor},transferCount:A.length,transfers:A,counterparties:I}}async resolveContractAddressBySymbol(e,t,n){let r=e.toLowerCase(),o=await this.service.getWalletTokenBalances({address:t,chain:n});if(o.success&&o.data?.result?.length){let a=o.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}let s=await this.service.searchTokensByKey({key:e,chain:n});if(s.success&&s.data?.length){let i=s.data.find(c=>(c.symbol??"").toLowerCase()===r)??s.data[0];if(i?.token_address)return{address:i.token_address,symbol:i.symbol,name:i.name}}}buildCounterparties(e){let t=new Map,n=new Map;for(let o of e){let s=o.direction==="send"?t:n,a=o.counterparty.toLowerCase(),i=parseFloat(o.amount_formatted)||0,c=s.get(a);c?(c.total+=i,c.count+=1,!c.label&&o.counterparty_label&&(c.label=o.counterparty_label),!c.entity&&o.counterparty_entity&&(c.entity=o.counterparty_entity),o.block_timestamp<c.first&&(c.first=o.block_timestamp),o.block_timestamp>c.last&&(c.last=o.block_timestamp)):s.set(a,{label:o.counterparty_label,entity:o.counterparty_entity,total:i,count:1,first:o.block_timestamp,last:o.block_timestamp})}let r=o=>Array.from(o.entries()).sort((s,a)=>a[1].count-s[1].count).map(([s,a])=>({address:s,label:a.label,entity:a.entity,total_amount:a.total.toFixed(6).replace(/\.?0+$/,""),tx_count:a.count,first_seen:a.first,last_seen:a.last}));return{sent:r(t),received:r(n)}}parseBlock(e){if(typeof e=="number")return Number.isFinite(e)?e:void 0;if(typeof e=="string"&&e){let t=Number(e);return Number.isFinite(t)?t:void 0}}};var He=class extends v{name="get-wallet-nft-transfers";description=`Get all NFT transfers for a wallet from the dedicated Moralis NFT transfers endpoint. Each transfer includes direction (send/receive), the exact counterparty address, token ID, collection, contract type (ERC721/ERC1155), and optional last sale price. The response includes a 'counterparties' section aggregating unique senders/recipients with token IDs and collections. Use this tool for questions like: "Which NFTs did I sell and to whom?" (direction=send), "Who sent me NFTs from collection 0x\u2026?" (contractAddresses=[0x\u2026], direction=receive), "Show all transfers for my NFT collection 0x\u2026" (contractAddresses=[0x\u2026]), "What NFTs did I mint?" (direction=receive), "Show NFT sales with prices" (direction=send, includePrices=true). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"contractAddresses",type:"array",description:"Filter by one or more NFT contract addresses (0x\u2026). Use this to scope queries to a specific collection. Omit to return all NFT transfers.",required:!1,items:{type:"string"}},{name:"direction",type:"string",description:'Client-side direction filter: "send" (wallet sent the NFT), "receive" (wallet received it), "all" (default).',required:!1,default:"all"},{name:"limit",type:"number",description:"Page size (1-100, default 25).",required:!1,default:25},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"},{name:"includePrices",type:"boolean",description:"If true, include last_sale price data (buyer, seller, price, USD value, payment token) on each transfer. Useful for questions about NFT sale prices.",required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):25,a=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,i=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,c=this.parseBlock(e.fromBlock),u=this.parseBlock(e.toBlock),d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,m=(typeof e.order=="string"?e.order.toUpperCase():"")==="ASC"?"ASC":"DESC",h=typeof e.includePrices=="boolean"?e.includePrices:void 0,f=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",g=f==="send"?"send":f==="receive"?"receive":"all",y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(A=>typeof A=="string"&&!!A).map(A=>A.trim()):void 0,k=await this.service.getWalletNftTransfers({address:n,chain:r,contractAddresses:y,limit:s,fromDate:a,toDate:i,fromBlock:c,toBlock:u,cursor:d,order:m,includePrices:h});if(!k.success)return{error:k.error||"Failed to fetch NFT transfers"};let w=n.toLowerCase(),T=(k.data?.result||[]).map(A=>{let I=A.from_address.toLowerCase()===w,S=I?"send":"receive",_=I?A.to_address:A.from_address,C=I?A.to_address_label??void 0:A.from_address_label??void 0,q=I?A.to_address_entity??void 0:A.from_address_entity??void 0;return{...A,direction:S,counterparty:_,counterparty_label:C,counterparty_entity:q}}),P=g==="all"?T:T.filter(A=>A.direction===g),x=this.buildCounterparties(P);return{walletAddress:n,chain:r||"default",filters:{contractAddresses:y||null,direction:g,limit:s,order:m,fromDate:a||null,toDate:i||null,fromBlock:c??null,toBlock:u??null,includePrices:h??null},pagination:{cursor:k.data?.cursor??null,pageSize:k.data?.page_size??s,page:k.data?.page??null,hasMore:!!k.data?.cursor},transferCount:P.length,transfers:P,counterparties:x}}buildCounterparties(e){let t=new Map,n=new Map;for(let o of e){let s=o.direction==="send"?t:n,a=o.counterparty.toLowerCase(),i=o.token_name??o.token_address,c=s.get(a);c?(c.tokenIds.add(o.token_id),c.collections.add(i),c.count+=1,!c.label&&o.counterparty_label&&(c.label=o.counterparty_label),!c.entity&&o.counterparty_entity&&(c.entity=o.counterparty_entity),o.block_timestamp<c.first&&(c.first=o.block_timestamp),o.block_timestamp>c.last&&(c.last=o.block_timestamp)):s.set(a,{label:o.counterparty_label,entity:o.counterparty_entity,tokenIds:new Set([o.token_id]),collections:new Set([i]),count:1,first:o.block_timestamp,last:o.block_timestamp})}let r=o=>Array.from(o.entries()).sort((s,a)=>a[1].count-s[1].count).map(([s,a])=>({address:s,label:a.label,entity:a.entity,token_ids:Array.from(a.tokenIds),collections:Array.from(a.collections),tx_count:a.count,first_seen:a.first,last_seen:a.last}));return{sent:r(t),received:r(n)}}parseBlock(e){if(typeof e=="number")return Number.isFinite(e)?e:void 0;if(typeof e=="string"&&e){let t=Number(e);return Number.isFinite(t)?t:void 0}}};var on=class extends v{name="get-wallet-net-worth";description=`Get a wallet's total net worth in USD, with a per-chain breakdown. Returns: total_networth_usd (grand total across all queried chains), chains[] (each with chain, native_balance_formatted, native_balance_usd, token_balance_usd, networth_usd), and optional unsupported_chain_ids / unavailable_chains lists. Use this tool for questions like: "How much is my wallet worth?", "What is my total portfolio value?", "What is my net worth on Ethereum?", "Portfolio value across all chains?". Pass chains=["0x1","0x2105"] to query multiple chains at once; defaults to the connected chain. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Do NOT use for: individual token balances \u2192 get-wallet-token-balances; realized profit/loss \u2192 get-wallet-pnl-summary.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chains",type:"array",description:'One or more hex chain IDs to aggregate. Values: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea If omitted, defaults to the connected chain.',required:!1,items:{type:"string"}},{name:"excludeSpam",type:"boolean",description:"Exclude tokens flagged as spam. Default true for cleaner totals.",required:!1,default:!0},{name:"excludeUnverifiedContracts",type:"boolean",description:"Exclude unverified token contracts. Default false.",required:!1,default:!1},{name:"maxTokenInactivity",type:"number",description:"Exclude tokens that have been inactive for more than N days.",required:!1},{name:"minPairSideLiquidityUsd",type:"number",description:"Exclude tokens with pair-side liquidity below this USD threshold.",required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0;if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let r=Array.isArray(e.chains)?e.chains.filter(m=>typeof m=="string"&&!!m).map(m=>m.trim()):void 0,o=t?.chain,s=r&&r.length>0?r:o?[o]:void 0,a=typeof e.excludeSpam=="boolean"?e.excludeSpam:!0,i=typeof e.excludeUnverifiedContracts=="boolean"?e.excludeUnverifiedContracts:void 0,c=typeof e.maxTokenInactivity=="number"&&Number.isFinite(e.maxTokenInactivity)?e.maxTokenInactivity:void 0,u=typeof e.minPairSideLiquidityUsd=="number"&&Number.isFinite(e.minPairSideLiquidityUsd)?e.minPairSideLiquidityUsd:void 0,d=await this.service.getWalletNetWorth({address:n,chains:s,excludeSpam:a,excludeUnverifiedContracts:i,maxTokenInactivity:c,minPairSideLiquidityUsd:u});if(!d.success)return{error:d.error||"Failed to fetch wallet net worth"};let p=d.data;return{walletAddress:n,chainsQueried:s??["default"],filters:{excludeSpam:a,excludeUnverifiedContracts:i??null,maxTokenInactivity:c??null,minPairSideLiquidityUsd:u??null},totalNetWorthUsd:p.total_networth_usd,chains:p.chains,unsupportedChainIds:p.unsupported_chain_ids??[],unavailableChains:p.unavailable_chains??[]}}};var br=["all","7","30","60","90"],Ge=class extends v{name="get-wallet-pnl-summary";description=`Get a wallet's aggregate realized profit-and-loss summary across all tokens it has traded. Returns: total_count_of_trades, total_trade_volume (USD), total_realized_profit_usd, total_realized_profit_percentage, total_buys, total_sells, total_sold_volume_usd, total_bought_volume_usd. Use this tool for questions like: "How much profit have I made trading?", "What is my realized PnL?", "How many trades have I done this month?" (days=30), "Am I up or down overall?". IMPORTANT \u2014 this endpoint only supports mainnet: Ethereum ("0x1"), Base ("0x2105"), Polygon ("0x89"). Use days="all" (default), "7", "30", "60", or "90" to scope the time window. Do NOT use for: per-token breakdown \u2192 get-wallet-pnl; current portfolio value \u2192 get-wallet-token-balance; transfer history \u2192 get-wallet-history.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"days",type:"string",description:'Time window for the summary. Values: "all" (default, lifetime), "7", "30", "60", "90". Pass "30" when the user asks about the last month, "7" for the last week, etc.',required:!1,default:"all"}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=br.includes(o)?o:void 0,a=await this.service.getWalletPnlSummary({address:n,chain:r,days:s});if(!a.success)return{error:a.error||"Failed to fetch wallet PnL summary"};let i=a.data;return{walletAddress:n,chain:r||"default",days:s??"all",summary:{totalCountOfTrades:i.total_count_of_trades,totalTradeVolume:i.total_trade_volume,totalRealizedProfitUsd:i.total_realized_profit_usd,totalRealizedProfitPercentage:i.total_realized_profit_percentage,totalBuys:i.total_buys,totalSells:i.total_sells,totalBoughtVolumeUsd:i.total_bought_volume_usd,totalSoldVolumeUsd:i.total_sold_volume_usd}}}};var wr=["all","7","30","60","90"],rn=25,Ke=class extends v{name="get-wallet-pnl";description=`Get a wallet's realized profit-and-loss broken down per ERC-20 token. Each entry includes: token_address, name, symbol, decimals, logo, avg_buy_price_usd, avg_sell_price_usd, total_usd_invested, total_tokens_bought, total_tokens_sold, total_sold_usd, avg_cost_of_quantity_sold, count_of_trades, realized_profit_usd, realized_profit_percentage, total_buys, total_sells, possible_spam. Use this tool for questions like: "Which tokens made me the most money?", "What are my best/worst trades?", "How much profit did I make on USDC?" (tokenSymbols=["USDC"]), "PnL for PEPE and DEGEN this month" (tokenSymbols=["PEPE","DEGEN"], days=30), "PnL breakdown for each token I traded this month" (days=30). IMPORTANT \u2014 this endpoint ONLY supports mainnet: Ethereum ("0x1"), Polygon ("0x89"), Base ("0x2105"). Use days="all" (default), "7", "30", "60", or "90" for the time window. Pass tokenSymbols (by ticker, auto-resolved) and/or tokenAddresses (raw 0x\u2026) to restrict to specific tokens \u2014 combined total capped at ${rn}. Do NOT use for: aggregate summary \u2192 get-wallet-pnl-summary; current balances \u2192 get-wallet-token-balances; transfer history \u2192 get-wallet-token-transfers.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"days",type:"string",description:'Time window for the breakdown. Values: "all" (default, lifetime), "7", "30", "60", "90".',required:!1,default:"all"},{name:"tokenSymbols",type:"array",description:`Optional list of token symbols (e.g. ["USDC", "PEPE"]). Auto-resolved to contract addresses via the connected wallet's balances first, then Pantograph search. Symbols that can't be resolved are skipped. Merged with tokenAddresses; combined total capped at ${rn}.`,required:!1,items:{type:"string"}},{name:"tokenAddresses",type:"array",description:`Optional list of raw ERC-20 contract addresses (0x\u2026). Use when the exact addresses are already known. Merged with tokenSymbols; combined total capped at ${rn}.`,required:!1,items:{type:"string"}}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=wr.includes(o)?o:void 0,a=Array.isArray(e.tokenSymbols)?e.tokenSymbols.filter(f=>typeof f=="string"&&!!f.trim()).map(f=>f.trim()):[],i=Array.isArray(e.tokenAddresses)?e.tokenAddresses.filter(f=>typeof f=="string"&&!!f.trim()).map(f=>f.trim()):[],c=[],u=[];a.length>0&&(await Promise.all(a.map(g=>this.resolveContractAddress(g,r,n)))).forEach((g,y)=>{g?c.push({symbol:a[y],address:g.address}):u.push(a[y])});let d=kr([...c.map(f=>f.address),...i]).slice(0,rn),p=d.length>0?d:void 0,m=await this.service.getWalletPnl({address:n,chain:r,days:s,tokenAddresses:p});if(!m.success)return{error:m.error||"Failed to fetch wallet PnL"};let h=m.data?.result??[];return{walletAddress:n,chain:r||"default",days:s??"all",filters:{tokenAddresses:p??null,resolvedSymbols:c.length>0?c:null,unresolvedSymbols:u.length>0?u:null},tokenCount:h.length,tokens:h}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};function kr(l){let e=new Set,t=[];for(let n of l){let r=n.toLowerCase();e.has(r)||(e.add(r),t.push(n))}return t}var Tr=BigInt(2)**BigInt(255),je=class extends v{name="get-wallet-approvals";description=`List every active ERC-20 token approval granted by a wallet \u2014 i.e. which contracts can still spend the wallet's tokens. For each approval, returns: the token (address, name, symbol, logo, current balance, USD price, USD at risk, possible_spam, verified_contract), the spender (address, label, entity \u2014 e.g. "Uniswap V3 Router", "Permit2"), the approved amount (value / value_formatted / isUnlimited flag), and when the approval was granted (block number, timestamp, transaction hash). Also returns aggregates: total approvals, unlimited-approval count, total USD at risk, and approvals flagged as risky (spam tokens, unverified contracts, or unlimited allowance to unlabeled spenders). Use for questions like: "What approvals do I have?", "Show my token approvals", "Am I exposed to any risky approvals?", "Which contracts can drain my wallet?", "Do I have any unlimited approvals?", "Revoke approvals / token allowances". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Paginated \u2014 pass cursor to fetch the next page.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"limit",type:"number",description:"Page size (1-100, default 100).",required:!1,default:100},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):100,a=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,i=await this.service.getWalletApprovals({address:n,chain:r,limit:s,cursor:a});if(!i.success)return{error:i.error||"Failed to fetch wallet approvals"};let c=i.data,d=(c.result||[]).map(f=>vr(f)),p=0,m=0,h=[];for(let f of d){f.isUnlimited&&(m+=1);let g=parseFloat(f.token.usdAtRisk??"");Number.isFinite(g)&&(p+=g);let y=f.token.possibleSpam||f.token.verifiedContract===!1,k=!f.spender.label&&!f.spender.entity;(y||f.isUnlimited&&k)&&h.push(f)}return{walletAddress:n,chain:r||"default",filters:{limit:s,cursor:a??null},pagination:{cursor:c.cursor??null,pageSize:c.page_size??s,page:c.page??null,hasMore:!!c.cursor},summary:{totalApprovals:d.length,unlimitedApprovals:m,totalUsdAtRisk:p,riskyApprovalCount:h.length},approvals:d,riskyApprovals:h}}};function vr(l){let e=Sr(l.value);return{blockNumber:l.block_number,blockTimestamp:l.block_timestamp??null,transactionHash:l.transaction_hash??null,value:l.value,valueFormatted:l.value_formatted??null,isUnlimited:e,token:{address:l.token.address,addressLabel:l.token.address_label??null,name:l.token.name??null,symbol:l.token.symbol??null,logo:l.token.logo??null,possibleSpam:!!l.token.possible_spam,verifiedContract:l.token.verified_contract??null,currentBalance:l.token.current_balance??null,currentBalanceFormatted:l.token.current_balance_formatted??null,usdPrice:l.token.usd_price??null,usdAtRisk:l.token.usd_at_risk??null},spender:{address:l.spender.address,label:l.spender.address_label??null,entity:l.spender.entity??null,entityLogo:l.spender.entity_logo??null}}}function Sr(l){if(!l)return!1;try{return BigInt(l)>=Tr}catch{return!1}}var Ye=class extends v{name="get-wallet-defi-summary";description=`Get a wallet's DeFi protocols summary: which protocols the wallet is using, total USD value locked across all protocols, total unclaimed rewards/fees, number of active protocols, and total number of positions. Returns a per-protocol breakdown (protocol name, logo, url, positions count, usd value, unclaimed). Use for questions like: "Which DeFi protocols am I using?", "What's my total DeFi value?", "How much do I have in DeFi?", "Show my DeFi portfolio overview", "Unclaimed rewards across protocols". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Do NOT use for: detailed per-position data \u2192 get-wallet-defi-positions; positions inside a single protocol \u2192 get-wallet-defi-protocol-positions.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=await this.service.getWalletDefiSummary({address:n,chain:r});if(!o.success)return{error:o.error||"Failed to fetch wallet DeFi summary"};let s=o.data;return{walletAddress:n,chain:r||"default",activeProtocols:s.active_protocols,totalPositions:s.total_positions,totalUsdValue:s.total_usd_value,totalUnclaimedUsdValue:s.total_unclaimed_usd_value,protocols:(s.protocols||[]).map(a=>({protocolName:a.protocol_name,protocolId:a.protocol_id,protocolUrl:a.protocol_url,protocolLogo:a.protocol_logo,positions:a.positions,totalUsdValue:a.total_usd_value,totalUnclaimedUsdValue:a.total_unclaimed_usd_value,accountHealthFactor:a.account_health_factor??null}))}}};var ze=class extends v{name="get-wallet-defi-positions";description='Get every DeFi position a wallet holds across all supported protocols, with full detail: protocol identity, position label (e.g. "Liquidity Provision", "Supply", "Borrow", "Staking"), tokens involved (with balances and USD values), balance_usd, total_unclaimed_usd_value, and position_details (fee tier, liquidity, APY, price range, health factor, etc.). Use for questions like: "Show my DeFi positions", "What LP positions do I have?", "What am I staking?", "Do I have any borrows?", "List all my DeFi holdings with details". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Do NOT use for: aggregate totals \u2192 get-wallet-defi-summary; positions filtered to a single protocol \u2192 get-wallet-defi-protocol-positions.';category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=await this.service.getWalletDefiPositions({address:n,chain:r});if(!o.success)return{error:o.error||"Failed to fetch wallet DeFi positions"};let s=o.data||[];return{walletAddress:n,chain:r||"default",positionCount:s.length,positions:s.map(a=>xr(a))}}};function xr(l){return{protocolName:l.protocol_name,protocolId:l.protocol_id,protocolUrl:l.protocol_url,protocolLogo:l.protocol_logo,accountHealthFactor:l.account_data?.health_factor??null,position:Pr(l.position)}}function Pr(l){return{label:l.label,address:l.address,balanceUsd:l.balance_usd,totalUnclaimedUsdValue:l.total_unclaimed_usd_value,tokens:(l.tokens||[]).map(e=>({tokenType:e.token_type,name:e.name,symbol:e.symbol,contractAddress:e.contract_address,decimals:e.decimals,logo:e.logo,thumbnail:e.thumbnail,balance:e.balance,balanceFormatted:e.balance_formatted,usdPrice:e.usd_price,usdValue:e.usd_value})),details:l.position_details??null}}var Qe=class extends v{name="get-wallet-defi-protocol-positions";description='Get every position a wallet holds inside a specific DeFi protocol, with full detail. Returns the protocol identity, totals (total_usd_value, total_unclaimed_usd_value), and an array of positions (label, tokens, balance_usd, unclaimed, fee tier, APY, health factor, etc.). Use when the user names a specific protocol, e.g. "my Uniswap v3 positions", "what do I have on Aave v3", "show my Lido staking", "my Curve pools", "Pendle positions". The "protocol" argument is the Moralis protocol id (kebab-case), e.g. uniswap-v2, uniswap-v3, pancakeswap-v2, pancakeswap-v3, quickswap-v2, quickswap-v3, sushiswap-v2, aave-v2, aave-v3, aave-lido, fraxswap-v1, fraxswap-v2, lido, makerdao, eigenlayer, pendle, etherfi, rocketpool, sparkfi, takara-lend, neverland, kintsu. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Do NOT use for: cross-protocol view \u2192 get-wallet-defi-positions; totals only \u2192 get-wallet-defi-summary.';category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"protocol",type:"string",description:'Moralis protocol identifier in kebab-case. Examples: "uniswap-v2", "uniswap-v3", "pancakeswap-v2", "pancakeswap-v3", "quickswap-v2", "quickswap-v3", "sushiswap-v2", "aave-v2", "aave-v3", "aave-lido", "fraxswap-v1", "fraxswap-v2", "lido", "makerdao", "eigenlayer", "pendle", "etherfi", "rocketpool", "sparkfi", "takara-lend", "neverland", "kintsu"Use get-wallet-defi-summary to discover which protocols a wallet uses.',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=e.protocol?.trim(),o=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};if(!r)return{error:'Protocol identifier is required (e.g. "uniswap-v3", "aave-v3").'};let s=await this.service.getWalletDefiProtocolPositions({address:n,protocol:r,chain:o});if(!s.success)return{error:s.error||"Failed to fetch wallet DeFi protocol positions"};let a=s.data;return a?{walletAddress:n,chain:o||"default",protocolName:a.protocol_name,protocolId:a.protocol_id,protocolUrl:a.protocol_url,protocolLogo:a.protocol_logo,totalUsdValue:a.total_usd_value,totalUnclaimedUsdValue:a.total_unclaimed_usd_value,accountHealthFactor:a.account_data?.health_factor??null,positionCount:(a.positions||[]).length,positions:(a.positions||[]).map(Ar)}:{error:"No data returned for this protocol"}}};function Ar(l){return{label:l.label,address:l.address,balanceUsd:l.balance_usd,totalUnclaimedUsdValue:l.total_unclaimed_usd_value,tokens:(l.tokens||[]).map(e=>({tokenType:e.token_type,name:e.name,symbol:e.symbol,contractAddress:e.contract_address,decimals:e.decimals,logo:e.logo,thumbnail:e.thumbnail,balance:e.balance,balanceFormatted:e.balance_formatted,usdPrice:e.usd_price,usdValue:e.usd_value})),details:l.position_details??null}}function _r(l){try{return(Number(BigInt(l))/1e18).toFixed(10).replace(/\.?0+$/,"")||"0"}catch{return l}}var Xe=class extends v{name="get-transaction-by-hash";description='Get full decoded details of a single EVM transaction by its hash. Returns: status (success/failed), timestamp, from/to addresses with labels and entity names, native value transferred (ETH), transaction fee, gas used, decoded function call with params, and decoded event logs (ERC-20 transfers, swaps, approvals, etc.). Use this when the user asks: "what did this transaction do?", "explain tx 0x\u2026", "was this transaction successful?", "who sent/received in this tx?", "what function was called?". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"transactionHash",type:"string",description:"The transaction hash to look up (0x\u2026).",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.transactionHash,r=R(e.chain,t);if(!n)return{error:"Transaction hash is required."};let o=await this.service.getTransactionByHash({transactionHash:n,chain:r});if(!o.success||!o.data)return{error:o.error||"Failed to fetch transaction"};let s=o.data,a=(s.logs??[]).filter(c=>!!c.decoded_event).map(c=>({contract:c.address,signature:c.decoded_event.signature,label:c.decoded_event.label,params:c.decoded_event.params})),i=s.logs.map(c=>({address:c.address,decodedEvent:c.decoded_event?{signature:c.decoded_event.signature,label:c.decoded_event.label,params:c.decoded_event.params}:null}));return{hash:s.hash,status:s.receipt_status==="1"?"success":s.receipt_status==="0"?"failed":"unknown",timestamp:s.block_timestamp,blockNumber:s.block_number,from:{address:s.from_address,label:s.from_address_label||null,entity:s.from_address_entity||null},to:s.to_address?{address:s.to_address,label:s.to_address_label||null,entity:s.to_address_entity||null}:null,valueEth:_r(s.value||"0"),fee:s.transaction_fee,gasUsed:s.receipt_gas_used,gasPriceGwei:s.gas_price?(Number(s.gas_price)/1e9).toFixed(4):null,decodedCall:s.decoded_call?{signature:s.decoded_call.signature,label:s.decoded_call.label,params:s.decoded_call.params}:null,decodedEvents:a,contractCreated:s.receipt_contract_address||null,logs:i}}};var ee=class extends v{kind="data";category="nft";noSuggestions=!0;parameters=[];linkPhrase="this website";url;constructor(e){super();let t=e?.url?.trim();this.url=t||void 0}async run(){let e=this.url?`[${this.linkPhrase}](${this.url})`:this.linkPhrase,t=this.template.replace("{link}",e);return{message:t,_instructions:`This is a fixed notice. Output it to the user EXACTLY as written below \u2014 in English, without translating, rephrasing, summarising, or adding/removing any words. Preserve the Markdown link verbatim (do not change, wrap, or drop the URL or the linked words). Do not mention tools. Reply with only this message:
|
|
16
|
+
|
|
17
|
+
`+t}}};var Je=class extends ee{name="send-nft";description='Use when the user wants to SEND / transfer an NFT ("send my NFT", "transfer an NFT", "g\u1EEDi NFT"). Returns a fixed message directing them to select and send the NFT on the website. Takes no arguments.';template="Please select the NFT you would like to send from the list and proceed via {link}."};var Ze=class extends ee{name="get-wallet-nfts";description='Use when the user wants to view their NFTs / NFT collection / holdings ("my NFTs", "show my NFTs", "what NFTs do I own", "NFT collection"). Returns a fixed notice directing them to the NFT website. Takes no arguments.';template="Please view your NFT collection on {link} for more details"};var et=class extends ee{name="get-nft-contract-info";description="Use when the user asks about an NFT collection / contract (name, symbol, type, floor, socials). Returns a fixed notice directing them to the NFT website. Takes no arguments.";template="Please view your NFT collection on {link} for more details"};var tt=class extends ee{name="get-nft-metadata";description='Use when the user asks about a specific NFT \u2014 its details, traits, image, or floor price ("show NFT #1234 of collection X", "traits of this NFT", "floor price of this NFT"). Returns a fixed notice directing them to the NFT website. Takes no arguments.';template="Please view your NFT collection on {link} for more details"};var nt=class extends v{name="gemini-search-ai";description="Ask a search-grounded Gemini model for blockchain / crypto insights that no dedicated tool can answer. Use this as a LAST-RESORT fallback when the specialised data tools are insufficient. Backed by live Google Search grounding \u2014 good for: market analysis, category/theme queries (meme, AI, gaming, L2, RWA), DeFi protocol explanations, cross-chain comparisons, trend / sentiment, recent news or announcements, gas insights, NFT collection lore, and any open-ended crypto reasoning question. Chain-agnostic \u2014 write the chain (Ethereum, BSC, Base, \u2026) and any addresses, time range, or token symbols directly into the prompt. Do NOT use this for: token balances \u2192 get-wallet-token-balances; token prices/metadata \u2192 get-token-info; NFT holdings \u2192 get-wallet-nfts; NFT contract info \u2192 get-nft-contract-info; transaction details \u2192 get-transaction-by-hash; wallet history \u2192 get-wallet-history; token transfers \u2192 get-wallet-token-transfers; NFT transfers \u2192 get-wallet-nft-transfers; token holders \u2192 get-token-holders; wallet PnL \u2192 get-wallet-pnl or get-wallet-pnl-summary; pool / liquidity data \u2192 pool-agent tools.";category="blockchain-ai";parameters=[{name:"prompt",type:"string",description:'A clear, self-contained English question for the AI. Must include every detail needed to answer without the conversation context: chain (Ethereum / BSC / Base / \u2026), wallet or contract addresses, token symbols, time range, what to compare or rank. Instruct the model to use up-to-date web sources when relevant. Example: "List the top 10 meme-category tokens on Ethereum right now, ranked by 24h trading volume, using up-to-date market data."',required:!0}];llm;constructor(e){super(),this.llm=new le(e??{})}async run(e,t){let n=typeof e.prompt=="string"?e.prompt.trim():"";if(!n)return{error:"Missing required parameter: prompt"};let r=[];t?.walletAddress&&r.push(`- Connected wallet address: ${t.walletAddress}`),t?.chain&&r.push(`- Current chain: ${t.chain}`),r.push("Use this context only when relevant to the question. Do not invent on-chain values.");let o=Date.now(),s=[{role:"system",content:`[USER CONTEXT]
|
|
18
|
+
${r.join(`
|
|
19
|
+
`)}`,timestamp:o},{role:"user",content:n,timestamp:o}];try{return{source:"gemini-search-ai",answer:(await this.llm.chat(s,void 0,{googleSearch:!0,maxRetries:1})).text?.trim()??""}}catch(a){return{error:a instanceof Error?a.message:String(a)}}}};var Cr="https://interface.gateway.uniswap.org",Ur="/v2/uniswap.explore.v1.ExploreStatsService/ExploreStats",lo="/v2/Search.v1.SearchService/SearchTokens",Rr="/v1/graphql",Te=3,sn=800,Er={"0x1":"ETHEREUM","0xa":"OPTIMISM","0x38":"BNB","0x89":"POLYGON","0x2105":"BASE","0xa4b1":"ARBITRUM","0xa86a":"AVALANCHE","0xe708":"LINEA"},Nr=`query V3Pool($chain: Chain!, $address: String!) {
|
|
20
|
+
v3Pool(chain: $chain, address: $address) {
|
|
21
|
+
id
|
|
22
|
+
protocolVersion
|
|
23
|
+
address
|
|
24
|
+
feeTier
|
|
25
|
+
token0 { ...SimpleTokenDetails ...TokenPrice __typename }
|
|
26
|
+
token0Supply
|
|
27
|
+
token1 { ...SimpleTokenDetails ...TokenPrice __typename }
|
|
28
|
+
token1Supply
|
|
29
|
+
txCount
|
|
30
|
+
volume24h: cumulativeVolume(duration: DAY) { value __typename }
|
|
31
|
+
historicalVolume(duration: WEEK) { value timestamp __typename }
|
|
32
|
+
totalLiquidity { value __typename }
|
|
33
|
+
totalLiquidityPercentChange24h { value __typename }
|
|
34
|
+
__typename
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fragment SimpleTokenDetails on Token {
|
|
39
|
+
...TokenBasicInfoParts
|
|
40
|
+
project { id isSpam logoUrl name safetyLevel __typename }
|
|
41
|
+
__typename
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fragment TokenBasicInfoParts on Token {
|
|
45
|
+
id address chain decimals name standard symbol isBridged __typename
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fragment TokenPrice on Token {
|
|
49
|
+
id
|
|
50
|
+
market(currency: USD) { id price { id value __typename } __typename }
|
|
51
|
+
__typename
|
|
52
|
+
}`,po=`
|
|
53
|
+
fragment SimpleTokenDetails on Token {
|
|
54
|
+
...TokenBasicInfoParts
|
|
55
|
+
project { id isSpam logoUrl name safetyLevel __typename }
|
|
56
|
+
__typename
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fragment TokenBasicInfoParts on Token {
|
|
60
|
+
id address chain decimals name standard symbol isBridged __typename
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fragment TokenPrice on Token {
|
|
64
|
+
id
|
|
65
|
+
market(currency: USD) { id price { id value __typename } __typename }
|
|
66
|
+
__typename
|
|
67
|
+
}`,Ir=`query V2Pair($chain: Chain!, $address: String!) {
|
|
68
|
+
v2Pair(chain: $chain, address: $address) {
|
|
69
|
+
id
|
|
70
|
+
protocolVersion
|
|
71
|
+
address
|
|
72
|
+
token0 { ...SimpleTokenDetails ...TokenPrice __typename }
|
|
73
|
+
token0Supply
|
|
74
|
+
token1 { ...SimpleTokenDetails ...TokenPrice __typename }
|
|
75
|
+
token1Supply
|
|
76
|
+
txCount
|
|
77
|
+
volume24h: cumulativeVolume(duration: DAY) { value __typename }
|
|
78
|
+
historicalVolume(duration: WEEK) { value timestamp __typename }
|
|
79
|
+
totalLiquidity { value __typename }
|
|
80
|
+
totalLiquidityPercentChange24h { value __typename }
|
|
81
|
+
__typename
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
${po}`,Dr=`query V4Pool($chain: Chain!, $poolId: String!) {
|
|
85
|
+
v4Pool(chain: $chain, poolId: $poolId) {
|
|
86
|
+
id
|
|
87
|
+
protocolVersion
|
|
88
|
+
feeTier
|
|
89
|
+
isDynamicFee
|
|
90
|
+
tickSpacing
|
|
91
|
+
poolId
|
|
92
|
+
hook { id address __typename }
|
|
93
|
+
token0 { ...SimpleTokenDetails ...TokenPrice __typename }
|
|
94
|
+
token0Supply
|
|
95
|
+
token1 { ...SimpleTokenDetails ...TokenPrice __typename }
|
|
96
|
+
token1Supply
|
|
97
|
+
txCount
|
|
98
|
+
volume24h: cumulativeVolume(duration: DAY) { value __typename }
|
|
99
|
+
historicalVolume(duration: WEEK) { value timestamp __typename }
|
|
100
|
+
totalLiquidity { value __typename }
|
|
101
|
+
totalLiquidityPercentChange24h { value __typename }
|
|
102
|
+
__typename
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
${po}`,Lr={"0x1":1,"0xa":10,"0x38":56,"0x89":137,"0x2105":8453,"0xa4b1":42161,"0xa86a":43114,"0xe708":59144};function an(l){return new Promise(e=>setTimeout(e,l))}function In(l){if(!l)return 1;let e=l.toLowerCase();return Lr[e]??1}function Dn(l){if(!l)return"ETHEREUM";let e=l.toLowerCase();return Er[e]??"ETHEREUM"}function co(l,e){if(!l)return[];switch(e){case"V2":return l.poolStatsV2??[];case"V3":return l.poolStatsV3??[];case"V4":return l.poolStatsV4??[]}}function uo(l,e){switch(e){case"volume1Day":return l.volume1Day?.value??0;case"volume1Week":return l.volume1Week?.value??0;case"volume30Day":return l.volume30Day?.value??0;case"txCount":return l.txCount??0;case"apr":return l.apr??0;default:return l.totalLiquidity?.value??0}}function mo(l){let e=l.volume1Day?.value,t=l.totalLiquidity?.value;return typeof l.feeTier!="number"||!e||!t?void 0:l.feeTier/1e4*e/t*365}var Q=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??Cr).replace(/\/$/,"")}async getExploreStats(e){let t=In(e?.chain),n=e?.multichain===!0,r=encodeURIComponent(JSON.stringify({chainId:String(t),multichain:n})),o=`${this.baseUrl}${Ur}?connect=v1&encoding=json&message=${r}`,s="Unknown error";for(let a=1;a<=Te;a++)try{let i=await fetch(o,{headers:{accept:"*/*",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!i.ok)throw new Error(`HTTP ${i.status}`);return{success:!0,data:await i.json()}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<Te&&await an(sn)}return{success:!1,error:s}}async getTopPools(e){let t=e?.protocolVersion??"V3",n=e?.limit&&e.limit>0?Math.floor(e.limit):10,r=e?.sortBy??"tvl",o=await this.getExploreStats({chain:e?.chain,multichain:e?.multichain});return!o.success||!o.data?{success:!1,error:o.error||"Failed to fetch ExploreStats"}:{success:!0,data:co(o.data.stats,t).map(i=>({...i,apr:mo(i),fee24hUsd:i.volume1Day?.value&&i.feeTier?i.feeTier/1e4*i.volume1Day.value:void 0})).sort((i,c)=>uo(c,r)-uo(i,r)).slice(0,n)}}async getV3PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V3Pool",query:Nr,variables:{chain:Dn(e.chain),address:t},pickField:"v3Pool"}):{success:!1,error:"Pool address is required"}}async getV2PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V2Pair",query:Ir,variables:{chain:Dn(e.chain),address:t},pickField:"v2Pair"}):{success:!1,error:"Pair address is required"}}async getV4PoolDetail(e){let t=e.poolId?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V4Pool",query:Dr,variables:{chain:Dn(e.chain),poolId:t},pickField:"v4Pool"}):{success:!1,error:"V4 poolId is required"}}async fetchPoolDetailGraphQL(e){let t=`${this.baseUrl}${Rr}`,n=JSON.stringify({operationName:e.operationName,variables:e.variables,query:e.query}),r="Unknown error";for(let o=1;o<=Te;o++)try{let s=await fetch(t,{method:"POST",headers:{accept:"*/*","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web","_dd-custom-header-graph-ql-operation-name":e.operationName,"_dd-custom-header-graph-ql-operation-type":"query"},body:n});if(!s.ok)throw new Error(`HTTP ${s.status}`);let a=await s.json();if(a.errors)throw new Error(`GraphQL error: ${JSON.stringify(a.errors)}`);let i=a.data?.[e.pickField]??void 0;return i?{success:!0,data:i}:{success:!1,error:"Pool not found"}}catch(s){r=s instanceof Error?s.message:"Unknown error",o<Te&&await an(sn)}return{success:!1,error:r}}async searchTokens(e){let t=e.query?.trim();if(!t)return{success:!1,error:"Search query is required"};if(D(t))return{success:!1,error:"Address is invalid for token search. If you pasted a 0x address, it may be a Uniswap pool \u2014 try the pool search tool instead."};let n=In(e.chain),r=e.page&&e.page>0?Math.floor(e.page):1,o=e.size&&e.size>0?Math.min(Math.floor(e.size),50):15,s=`${this.baseUrl}${lo}`,a=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"TOKEN",page:r,size:o}),i="Unknown error";for(let c=1;c<=Te;c++)try{let u=await fetch(s,{method:"POST",headers:{accept:"*/*","connect-protocol-version":"1","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"},body:a});if(!u.ok)throw new Error(`HTTP ${u.status}`);return{success:!0,data:(await u.json()).tokens??[]}}catch(u){i=u instanceof Error?u.message:"Unknown error",c<Te&&await an(sn)}return{success:!1,error:i}}async searchPools(e){let t=e.query?.trim();if(!t)return{success:!1,error:"Search query is required"};let n=In(e.chain),r=e.page&&e.page>0?Math.floor(e.page):1,o=e.size&&e.size>0?Math.min(Math.floor(e.size),50):15,s=e.protocolVersion??"V3",a=e.feeTier&&e.feeTier>0?Math.floor(e.feeTier):void 0,i=`${this.baseUrl}${lo}`,c=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"POOL",page:r,size:o}),[u,d]=await Promise.allSettled([this.fetchSearchPools(i,c),this.getExploreStats({chain:e.chain})]);if(u.status==="rejected"||!u.value.success)return{success:!1,error:u.status==="rejected"?String(u.reason):u.value.error??"Failed to search pools"};let p=t.toLowerCase().split(/\s+/).filter(Boolean),m=u.value.data??[];s!=="ALL"&&(m=m.filter(g=>g.protocolVersion===s)),a!=null&&(m=m.filter(g=>g.feeTier===a));let h=new Map;for(let g of m)g.id&&h.set(g.id.toLowerCase(),g);if(d.status==="fulfilled"&&d.value.success&&d.value.data){let g=d.value.data.stats,y=s==="ALL"?["V2","V3","V4"]:[s];for(let k of y)for(let w of co(g,k)){if(!w.id||a!=null&&w.feeTier!==a)continue;let b=(w.token0?.symbol??"").toLowerCase(),T=(w.token1?.symbol??"").toLowerCase(),P=(w.token0?.name??"").toLowerCase(),x=(w.token1?.name??"").toLowerCase();if(!p.some(C=>b.includes(C)||T.includes(C)||P.includes(C)||x.includes(C)))continue;let I=mo(w),S=w.id.toLowerCase(),_=h.get(S);_?h.set(S,{..._,...I!=null?{apr:I}:{},...w.totalLiquidity?.value!=null?{tvlUsd:w.totalLiquidity.value}:{},...w.volume1Day?.value!=null?{volume1DayUsd:w.volume1Day.value}:{},...w.txCount!=null?{txCount:w.txCount}:{}}):h.set(S,{id:w.id,protocolVersion:w.protocolVersion,feeTier:w.feeTier,token0:w.token0?{address:w.token0.address,symbol:w.token0.symbol,name:w.token0.name,decimals:w.token0.decimals}:void 0,token1:w.token1?{address:w.token1.address,symbol:w.token1.symbol,name:w.token1.name,decimals:w.token1.decimals}:void 0,...I!=null?{apr:I}:{},...w.totalLiquidity?.value!=null?{tvlUsd:w.totalLiquidity.value}:{},...w.volume1Day?.value!=null?{volume1DayUsd:w.volume1Day.value}:{},...w.txCount!=null?{txCount:w.txCount}:{}})}}return{success:!0,data:Array.from(h.values()).sort((g,y)=>this.matchScore(y,p)-this.matchScore(g,p))}}async fetchSearchPools(e,t){let n="Unknown error";for(let r=1;r<=Te;r++)try{let o=await fetch(e,{method:"POST",headers:{accept:"*/*","connect-protocol-version":"1","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"},body:t});if(!o.ok)throw new Error(`HTTP ${o.status}`);return{success:!0,data:(await o.json()).pools??[]}}catch(o){n=o instanceof Error?o.message:"Unknown error",r<Te&&await an(sn)}return{success:!1,error:n}}matchScore(e,t){let n=[e.token0?.symbol??"",e.token1?.symbol??""].map(o=>o.toLowerCase()),r=0;for(let o of t){let s=0;for(let a of n)a===o?s=Math.max(s,3):a.startsWith(o)?s=Math.max(s,2):a.includes(o)&&(s=Math.max(s,1));r+=s}return r}};var Mr=["tvl","volume1Day","volume1Week","volume30Day","txCount","apr"],Br=["V2","V3","V4"],ot=class extends v{name="get-top-pools";description=`List the top Uniswap liquidity pools on a given EVM chain, ranked by TVL by default. Returns each pool's address, fee tier, protocol version, both tokens (symbol, name, address, decimals), TVL, transaction count, and rolling volume buckets (1d / 1w / 30d). Use this tool for questions like: "top pools on Ethereum", "biggest Uniswap V3 pools on Base", "which pool has the most liquidity for USDC", "show pools with highest 24h volume on Arbitrum". Source: https://app.uniswap.org/explore (ExploreStats gateway). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea`;category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"protocolVersion",type:"string",description:'Uniswap protocol version to filter by: "V2", "V3", or "V4". Default is "V3" (the dataset with the richest stats). Use "V4" only when the user explicitly asks for V4 pools.',required:!1,default:"V3"},{name:"sortBy",type:"string",description:'Ranking metric: "tvl" (total value locked, default), "volume1Day", "volume1Week", "volume30Day", "txCount", or "apr" (estimated annualized fee yield). Pools are returned in descending order by this metric.',required:!1,default:"tvl"},{name:"limit",type:"number",description:"Maximum number of pools to return after sorting. Defaults to 10.",required:!1,default:10},{name:"multichain",type:"boolean",description:"When true, request Uniswap's multichain aggregated view instead of a single chain. Defaults to false. Most queries should leave this off.",required:!1,default:!1}];service;constructor(e){super(),this.service=new Q(e)}async run(e,t){let n=R(e.chain,t),r=this.parseProtocolVersion(e.protocolVersion),o=this.parseSortKey(e.sortBy),s=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),50):10,a=e.multichain===!0,i=await this.service.getTopPools({chain:n,protocolVersion:r,sortBy:o,limit:s,multichain:a});if(!i.success)return{error:i.error||"Failed to fetch top pools"};let c=(i.data??[]).map(u=>this.formatPool(u));return{_instructions:"Present the pools as a ranked list. For each pool show: rank, pair (token0/token1 symbols), apr(in %), fee tier (in %), TVL in USD, 24h volume, 24h fee revenue, and protocol version. Format numbers human-readably (e.g. $12.5M, 0.05%). Mention the chain and the sort metric in the intro line. If a value is null, omit it. Do NOT mention tool or API names.",chain:n,protocolVersion:r,sortBy:o,count:c.length,pools:c}}parseProtocolVersion(e){if(typeof e=="string"){let t=e.trim().toUpperCase();if(Br.includes(t))return t}return"V3"}parseSortKey(e){if(typeof e=="string"){let t=e.trim();if(Mr.includes(t))return t}return"tvl"}formatPool(e){return{apr:e.apr,address:e.id,chain:e.chain,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0,tvlUsd:e.totalLiquidity?.value,volume1DayUsd:e.volume1Day?.value,volume1WeekUsd:e.volume1Week?.value,volume30DayUsd:e.volume30Day?.value,txCount:e.txCount,token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.price?.value}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.price?.value}:void 0}}};var Or=365,$r=/^0x[a-fA-F0-9]{40}$/,rt=class extends v{name="get-pool-detail";description="Get detailed stats for a single Uniswap V3 pool, identified by token pair (and optional fee tier). Always runs a search first, filters by the provided symbols + fee tier, and only fetches detail when the filter resolves to exactly one pool. Otherwise returns a `candidates` list \u2014 show that to the user, ask which pool they want, then re-call this tool with the missing info. Detail response includes: pool address, fee tier (bps and %), both tokens (symbol, name, address, decimals, USD price), token0/token1 reserves, total TVL with 24h % change, 24h volume, weekly historical volume series, total tx count, and a derived APR estimate (fees \xF7 TVL \xD7 365). NEVER pass a pool address \u2014 the tool resolves the address itself by searching. Token contract addresses are NOT pool addresses. Source: https://app.uniswap.org. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea";category="blockchain-data";parameters=[{name:"token0Symbol",type:"string",description:`Symbol of one of the pool's tokens (e.g. "USDC"). Pair order does not matter \u2014 USDC/WETH and WETH/USDC resolve to the same pool. When both token0Symbol and token1Symbol are provided the search is filtered to that exact pair.`,required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Optional \u2014 when omitted the tool returns all pools containing token0Symbol so the user can pick.',required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points (bps): 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Optional \u2014 when omitted the tool returns all fee tiers for the pair so the user can pick.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new Q(e)}async run(e,t){let n=R(e.chain,t),r=typeof e.token0Symbol=="string"?e.token0Symbol.trim():"",o=typeof e.token1Symbol=="string"?e.token1Symbol.trim():"",s=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0;if(!r)return{error:"token0Symbol is required. Provide at least one token symbol; add token1Symbol and feeTier to narrow the result."};let a=o?`${r} ${o}`:r,i=await this.service.searchPools({query:a,chain:n,page:1,size:100,protocolVersion:"V3"});if(!i.success)return{error:i.error||"Pool search failed"};let c=i.data??[],d=c.filter(m=>this.matchesFilter(m,r,o,s,"ordered")),p=!1;if(d.length===0&&o){let m=c.filter(h=>this.matchesFilter(h,r,o,s,"reversed"));m.length>0&&(d=m,p=!0)}if(d.length===0)return{error:`No V3 pool matched ${this.describeQuery(r,o,s)} on chain ${n}. Verify the symbols and fee tier with the user.`};if(d.length===1){let m=d[0];if(!m.id||!$r.test(m.id))return{error:"Resolved pool has no valid address"};let h=await this.service.getV3PoolDetail({address:m.id,chain:n});return!h.success||!h.data?{error:h.error||"Failed to fetch pool detail"}:{_instructions:"Present pool details in a compact summary. Lead with the pair (token0/token1 symbols), fee tier (%), and chain. Then show: TVL (USD) with 24h % change, 24h volume (USD), 24h fees (USD), estimated APR (%), token0 reserve + USD price, token1 reserve + USD price, and total tx count. Optionally include the weekly historical volume as a sparkline-style bullet list. Format numbers human-readably (e.g. $12.5M, +1.23%, 0.05%). Omit null fields. Do NOT mention tool/API names.",pairOrderMatched:p?"reversed":"ordered",pool:this.formatPool(h.data)}}return{_instructions:"The filter matched multiple pools. Render them as a numbered list (pair, fee tier %, 24h volume, address) and ask the user which one they want to see. When they answer, re-call this tool with their selection \u2014 pass token0Symbol + token1Symbol + feeTier (use the exact feeTier in bps from the chosen candidate). Format numbers human-readably (e.g. $12.5M, 0.05%). Do NOT mention tool/API names.",chain:n,pairOrderMatched:p?"reversed":"ordered",reason:this.candidateReason(r,o,s),missing:this.missingFields(o,s),candidates:this.rankCandidates(d).slice(0,10).map(m=>this.formatCandidate(m))}}matchesFilter(e,t,n,r,o){if(r!=null&&e.feeTier!==r)return!1;let s=(e.token0?.symbol??"").toLowerCase(),a=(e.token1?.symbol??"").toLowerCase(),i=t.toLowerCase();if(n){let c=n.toLowerCase();return o==="reversed"?this.symbolIncludes(s,c)&&this.symbolIncludes(a,i):this.symbolIncludes(s,i)&&this.symbolIncludes(a,c)}return this.symbolIncludes(s,i)||this.symbolIncludes(a,i)}symbolIncludes(e,t){return!e||!t?!1:e.includes(t)||t.includes(e)}describeQuery(e,t,n){let r=t?`${e}/${t}`:e;return n!=null?`${r} at fee tier ${n} bps`:r}candidateReason(e,t,n){return t?n==null?`Multiple fee tiers exist for ${e}/${t}. Ask the user which fee tier they want.`:`Multiple ${e}/${t} pools share fee tier ${n} bps. Ask the user which one they meant.`:`Multiple pools contain ${e}. Ask the user for the other token (and fee tier).`}missingFields(e,t){let n=[];return e||n.push("token1Symbol"),t==null&&n.push("feeTier"),n}rankCandidates(e){return[...e].sort((t,n)=>(n.volumeUsd24hr??0)-(t.volumeUsd24hr??0))}formatCandidate(e){return{address:e.id,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0,volume24hUsd:e.volumeUsd24hr,pair:e.token0?.symbol&&e.token1?.symbol?`${e.token0.symbol}/${e.token1.symbol}`:void 0,token0:e.token0?{symbol:e.token0.symbol,name:e.token0.name,address:e.token0.address}:void 0,token1:e.token1?{symbol:e.token1.symbol,name:e.token1.name,address:e.token1.address}:void 0}}formatPool(e){let t=e.totalLiquidity?.value,n=e.volume24h?.value,r=e.feeTier,o=typeof r=="number"?r/1e4:void 0,s=typeof n=="number"&&typeof o=="number"?n*o/100:void 0,a=typeof s=="number"&&typeof t=="number"&&t>0?s/t*Or*100:void 0;return{address:e.address,protocolVersion:e.protocolVersion,feeTierBps:r,feeTierPercent:o,tvlUsd:t,tvlChange24hPercent:e.totalLiquidityPercentChange24h?.value,volume24hUsd:n,fees24hUsd:s,aprPercent:a,txCount:e.txCount,historicalVolumeWeek:this.formatHistorical(e.historicalVolume),token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.market?.price?.value??e.token0.price?.value,reserve:e.token0Supply,isSpam:e.token0.project?.isSpam,safetyLevel:e.token0.project?.safetyLevel}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.market?.price?.value??e.token1.price?.value,reserve:e.token1Supply,isSpam:e.token1.project?.isSpam,safetyLevel:e.token1.project?.safetyLevel}:void 0}}formatHistorical(e){if(!(!e||e.length===0))return e.map(t=>({timestamp:t.timestamp,volumeUsd:t.value}))}};var qr=["V2","V3","V4","ALL"],st=class extends v{name="search-pools";description='Search Uniswap liquidity pools on a given chain by free-text query \u2014 a SINGLE token symbol or token name. Returns matching pools with: pool address, fee tier (bps + %), protocol version, both tokens (symbol, name, address, decimals, logo), and short-window USD volume buckets (6h / 12h / 24h). Use this tool when the user names ONE token and wants pools related to it: "find WBTC pools on Optimism", "pools containing PEPE on Ethereum", "show USDC pools". Do NOT use for: (a) two-symbol pair lookups like "USDC/WETH 0.05%" \u2014 use get-pool-detail; (b) raw 0x addresses (token or pool) \u2014 use lookup-pool-by-address; (c) chain-wide rankings with no token mentioned \u2014 use get-top-pools. Source: https://app.uniswap.org search. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"query",type:"string",description:'Single token symbol or name to search pools by (e.g. "WBTC", "USDC", "pepe"). Do NOT pass a 0x address (use lookup-pool-by-address) or a two-symbol pair (use get-pool-detail).',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"protocolVersion",type:"string",description:'Filter results by Uniswap protocol version: "V2", "V3", "V4", or "ALL" to include every version. Default is "V3" \u2014 only switch when the user explicitly asks for V2, V4, or all versions.',required:!1,default:"V3"},{name:"page",type:"number",description:"1-based page index. Defaults to 1.",required:!1,default:1},{name:"size",type:"number",description:"Page size (max 50). Defaults to 15.",required:!1,default:15},{name:"feeTier",type:"number",description:'Optional fee tier filter in basis points (bps): 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. When provided, only pools with this exact fee tier are returned. Pass it whenever the user mentions a fee tier in the current message OR earlier in the conversation (e.g. "USDC/WETH 0.05% pool on Base" \u2192 feeTier=500). Omit otherwise.',required:!1}];service;constructor(e){super(),this.service=new Q(e)}async run(e,t){let n=typeof e.query=="string"?e.query.trim():"";if(!n)return{error:"Search query is required"};let r=R(e.chain,t),o=typeof e.page=="number"&&Number.isFinite(e.page)&&e.page>0?Math.floor(e.page):1,s=typeof e.size=="number"&&Number.isFinite(e.size)&&e.size>0?Math.min(Math.floor(e.size),50):15,a=this.parseProtocolFilter(e.protocolVersion),i=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)&&e.feeTier>0?Math.floor(e.feeTier):void 0,c=await this.service.searchPools({query:n,chain:r,page:o,size:s,protocolVersion:a,feeTier:i});if(!c.success)return{error:c.error||"Failed to search pools"};let u=(c.data??[]).map(d=>this.formatPool(d));return{_instructions:"Present the matching pools as a list. For each pool show: pair (token0/token1 symbols), fee tier (%), protocol version, and only the fields that have actual data \u2014 apr (%), TVL (USD), 24h volume (USD), tx count. Skip any field that is null/undefined \u2014 do not show zeros or dashes. Format numbers human-readably (e.g. $12.5M, 0.30%, 4.2%). Mention the search query and chain in the intro. Do NOT mention tool or API names.",query:n,chain:r,protocolVersion:a,feeTierBps:i,feeTierPercent:i!=null?i/1e4:void 0,page:o,size:s,count:u.length,pools:u}}parseProtocolFilter(e){if(typeof e=="string"){let t=e.trim().toUpperCase();if(qr.includes(t))return t}return"V3"}formatPool(e){let t={address:e.id,chainId:e.chainId,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0};e.apr!=null&&(t.apr=e.apr),e.tvlUsd!=null&&(t.tvlUsd=e.tvlUsd);let n=e.volume1DayUsd??e.volumeUsd24hr;n!=null&&(t.volume24hUsd=n),e.volumeUsd6hr!=null&&(t.volume6hUsd=e.volumeUsd6hr),e.volumeUsd12hr!=null&&(t.volume12hUsd=e.volumeUsd12hr),e.txCount!=null&&(t.txCount=e.txCount);let r=e.token0,o=e.token1;return r&&(t.token0={address:r.address,symbol:r.symbol,name:r.name,decimals:r.decimals}),o&&(t.token1={address:o.address,symbol:o.symbol,name:o.name,decimals:o.decimals}),t}};var Fr=365,ho=/^0x[a-fA-F0-9]{40}$/,at=class extends v{name="lookup-pool-by-address";description='Resolve an unknown 0x address as either a Uniswap pool or a token, then return the appropriate data: pool detail when the address is a pool, or the list of pools containing that token when the address is a token. Use this tool when the user pastes a single address and asks about "the pool" without saying which kind. For pool detail (when the address is a pool): pair, fee tier, TVL with 24h % change, 24h volume, 24h fees, estimated APR, token reserves with USD prices, and weekly volume series. For token resolution (when the address is a token): the resolved token (symbol, name, decimals) plus a ranked list of pools that include it (pair, fee tier, 24h volume, pool address). Source: https://app.uniswap.org. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea';category="blockchain-data";parameters=[{name:"address",type:"string",description:"A 0x-prefixed EVM address. May be a Uniswap pool contract OR a token contract \u2014 the tool figures out which.",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new Q(e)}async run(e,t){let n=R(e.chain,t),r=typeof e.address=="string"?e.address.trim():"",[o,s]=await Promise.all([this.service.searchPools({query:r,chain:n,page:1,size:5,protocolVersion:"ALL"}),this.service.searchTokens({query:r,chain:n,page:1,size:5})]),a=o.success?this.findPoolByAddress(o.data??[],r):void 0;if(a)return this.handlePoolHit(a,n);let i=s.success?this.findTokenByAddress(s.data??[],r):void 0;return i?this.handleTokenHit(i,n):!o.success&&!s.success?{error:o.error||s.error||"Failed to resolve address"}:{error:`Address ${r} did not match any pool or token Uniswap knows on chain ${n}. Verify the chain (the address may exist on a different network) or ask the user for context.`}}async handlePoolHit(e,t){if(!e.id)return{error:"Resolved pool has no identifier"};let n=e.protocolVersion??"V3",r=n==="V4"?await this.service.getV4PoolDetail({poolId:e.id,chain:t}):n==="V2"?ho.test(e.id)?await this.service.getV2PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V2 pair has no valid address"}:ho.test(e.id)?await this.service.getV3PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V3 pool has no valid address"};return!r.success||!r.data?{error:r.error||"Failed to fetch pool detail"}:{_instructions:`The address is a Uniswap ${n} pool. Present pool details in a compact summary. Lead with the pair (token0/token1 symbols), fee tier (%), protocol version, and chain. Then show: TVL (USD) with 24h % change, 24h volume (USD), 24h fees (USD), estimated APR (%), token0 reserve + USD price, token1 reserve + USD price, and total tx count. For V4 pools also mention tickSpacing, isDynamicFee, and the hook address if present. Format numbers human-readably (e.g. $12.5M, +1.23%, 0.05%). Omit null fields. Do NOT mention tool/API names.`,resolvedAs:"pool",chain:t,protocolVersion:n,pool:this.formatPoolDetail(r.data)}}async handleTokenHit(e,t){let n=e.symbol?.trim();if(!n)return{error:"Resolved token has no symbol \u2014 cannot search related pools."};let r=await this.service.searchPools({query:n,chain:t,page:1,size:100,protocolVersion:"V3"});if(!r.success)return{error:r.error||"Failed to fetch pools for resolved token"};let o=(e.address??"").toLowerCase(),a=[...(r.data??[]).filter(i=>{let c=(i.token0?.address??"").toLowerCase(),u=(i.token1?.address??"").toLowerCase();return c===o||u===o})].sort((i,c)=>(c.volumeUsd24hr??0)-(i.volumeUsd24hr??0)).slice(0,10);return{_instructions:"The address is a token, not a pool. Tell the user which token it is (symbol + name), then render the matching pools as a numbered list: pair (token0/token1 symbols), fee tier (%), protocol version, 24h volume (USD), and the pool address. Ask which pool they want detail on; when they answer, re-call this tool with that pool address to fetch the detail. Format numbers human-readably. Omit null fields. Do NOT mention tool/API names.",resolvedAs:"token",chain:t,protocolVersion:"V3",token:this.formatToken(e),poolCount:a.length,pools:a.map(i=>this.formatPoolCandidate(i))}}findPoolByAddress(e,t){let n=t.toLowerCase();return e.find(r=>(r.id??"").toLowerCase()===n)}findTokenByAddress(e,t){let n=t.toLowerCase();return e.find(r=>(r.address??"").toLowerCase()===n)}formatToken(e){return{address:e.address,symbol:e.symbol,name:e.name,decimals:e.decimals,chainId:e.chainId,isSpam:e.isSpam,safetyLevel:e.safetyLevel}}formatPoolCandidate(e){return{address:e.id,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0,volume24hUsd:e.volumeUsd24hr,pair:e.token0?.symbol&&e.token1?.symbol?`${e.token0.symbol}/${e.token1.symbol}`:void 0,token0:e.token0?{symbol:e.token0.symbol,name:e.token0.name,address:e.token0.address}:void 0,token1:e.token1?{symbol:e.token1.symbol,name:e.token1.name,address:e.token1.address}:void 0}}formatPoolDetail(e){let t=e.totalLiquidity?.value,n=e.volume24h?.value,r=e.feeTier,o=typeof r=="number"?r/1e4:void 0,s=typeof n=="number"&&typeof o=="number"?n*o/100:void 0,a=typeof s=="number"&&typeof t=="number"&&t>0?s/t*Fr*100:void 0;return{address:e.address??e.poolId,protocolVersion:e.protocolVersion,poolId:e.poolId,tickSpacing:e.tickSpacing,isDynamicFee:e.isDynamicFee,hookAddress:e.hook?.address,feeTierBps:r,feeTierPercent:o,tvlUsd:t,tvlChange24hPercent:e.totalLiquidityPercentChange24h?.value,volume24hUsd:n,fees24hUsd:s,aprPercent:a,txCount:e.txCount,historicalVolumeWeek:this.formatHistorical(e.historicalVolume),token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.market?.price?.value??e.token0.price?.value,reserve:e.token0Supply}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.market?.price?.value??e.token1.price?.value,reserve:e.token1Supply}:void 0}}formatHistorical(e){if(!(!e||e.length===0))return e.map(t=>({timestamp:t.timestamp,volumeUsd:t.value}))}};var Ln=require("js-sha3"),go=2n**96n;function fo(l,e,t){let n=l*l,r=go*go,o=n*10n**18n/r;return Number(o)/1e18*Math.pow(10,e-t)}function oe(l,e,t){return Math.pow(1.0001,l)*Math.pow(10,e-t)}function Pe(l,e,t){if(l<=0)throw new Error("priceToTick: price must be > 0");let n=l/Math.pow(10,e-t),r=Math.log(n)/Math.log(1.0001),o=Math.round(r);return Math.abs(r-o)<.001?o:Math.floor(r)}function pe(l,e,t="nearest"){let n=Math.abs(l-Math.round(l))<1e-6?Math.round(l):l,r=(n%e+e)%e;if(r===0)return n;let o=n-r,s=o+e;return t==="down"?o:t==="up"?s:n-o<s-n?o:s}var it=-887272,lt=887272;function he(l){return l<it?it:l>lt?lt:l}var Wr=/^0x[0-9a-f]+$/i;function te(l){return l.startsWith("0x")||l.startsWith("0X")?l.slice(2):l}function ln(l){let e=te(l).toLowerCase();if(e.length>64)throw new Error(`pad32: input too long (${e.length})`);return e.padStart(64,"0")}function cn(l){if(!Wr.test(l)||te(l).length!==40)throw new Error(`encodeAddress: invalid address ${l}`);return ln(l.toLowerCase())}function re(l,e=256){let t=typeof l=="bigint"?l:BigInt(l);if(t<0n)throw new Error("encodeUint: negative");let n=t.toString(16);if(n.length>e/4)throw new Error(`encodeUint: overflow ${e}`);return ln(n)}function Mn(l){let e=typeof l=="bigint"?l:BigInt(l);if(e>=0n)return ln(e.toString(16));let t=1n<<256n;return ln((t+e).toString(16))}function yo(l){return"0x"+(0,Ln.keccak_256)(Vr(l))}function Vr(l){let e=te(l),t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substring(n*2,n*2+2),16);return t}function ge(l){return"0x"+(0,Ln.keccak_256)(new TextEncoder().encode(l)).slice(0,8)}function Ft(l,e){let t=l.substring(e,e+64);return t?BigInt("0x"+t):0n}function Bn(l,e){let t=l.substring(e,e+64),n=BigInt("0x"+t),r=1n<<256n,o=1n<<23n;return n>=1n<<255n?Number(n-r):(n<o,Number(n))}function On(l,e){return"0x"+l.substring(e+24,e+64).toLowerCase()}var ve=class{config;constructor(e){this.config=e}hasIntegratedApproval(){return!1}};var Hr="https://exchange-api.keyring.app/admin/setting?configs=others",Gr="https://api.coinpool.app/config/bridgeProviderByChain",Kr="https://coinpool-api-op.bacoor-test001.xyz/config/bridgeProviderByChain";function jr(l){let e=null,t=null;return async()=>e||t||(t=(async()=>{try{return e=await l(),e}catch{return null}finally{t=null}})(),t)}async function wo(l){return await(await fetch(l,{method:"GET",headers:{Accept:"application/json"}})).json()}var Yr=jr(async()=>(await wo(Hr))?.others??null),un={prod:null,dev:null},Wt={prod:null,dev:null};async function zr(l){let e=l?"prod":"dev";if(un[e])return un[e];if(Wt[e])return Wt[e];let t=l?Gr:Kr;return Wt[e]=(async()=>{try{let n=await wo(t);return un[e]=n?.data??null,un[e]}catch{return null}finally{Wt[e]=null}})(),Wt[e]}function Qr(l){if(!l)return null;try{let e=JSON.parse(l);return e&&typeof e=="object"?e:null}catch{return null}}function bo(l){if(l==null)return 0;let e=typeof l=="number"?l:Number(l);return Number.isFinite(e)?e:0}function Xr(l){return l==="debridge"||l==="relay"}async function dn(l,e){let n=(await zr(e))?.[String(l)];return Xr(n)?n:null}async function ct(l,e){let t=await Yr();if(!t)return null;let n,r;if(l==="debridge")n=Qr(t.affiliateRecipient)?.[String(e)],r=bo(t.AFFILIATE_FEE_PERENT);else if(l==="relay")n=t.RELAY_AFFILIATE_FEE_RECIPIENT,r=bo(t.RELAY_AFFILIATE_FEE_PERCENT);else return null;return!n||r<=0?null:{affiliateFeeRecipient:n,affiliateFeePercent:r}}var ne={"0x1":"https://ethereum-rpc.publicnode.com","0xa":"https://mainnet.optimism.io","0x38":"https://bsc-dataseed.binance.org","0x89":'https://polygon-bor-rpc.publicnode.com"',"0x2105":"https://mainnet.base.org","0xa4b1":"https://arb1.arbitrum.io/rpc","0xa86a":"https://avalanche-c-chain-rpc.publicnode.com","0xe708":"https://rpc.linea.build"};function Jr(l){return ne[l.toLowerCase()]}var ko={};function $n(l){let e={};if(l)for(let[t,n]of Object.entries(l))typeof n=="string"&&n.trim()&&(e[t.toLowerCase()]=n.trim());ko=e}function ut(l){let e=l.toLowerCase();return ko[e]??ne[e]}async function fe(l,e,t){for(let r=0;r<=5;r++){let o=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:e,params:t})});if(o.status===429&&r<5){await new Promise(a=>setTimeout(a,2**r*500));continue}if(!o.ok)throw new Error(`RPC ${e} HTTP ${o.status}`);let s=await o.json();if(s.error)throw new Error(`RPC ${e}: ${s.error.message}`);if(s.result===void 0)throw new Error(`RPC ${e}: empty result`);return s.result}throw new Error(`RPC ${e}: exceeded max retries (429)`)}function To(l,e,t){return fe(l,"eth_call",[{to:e,data:t},"latest"])}async function qn(l,e,t){let n=ut(l);if(!n)return null;try{return await To(n,e,t)}catch{return null}}var Zr="https://dln.debridge.finance/v1.0",es="0x0000000000000000000000000000000000000000",dt=class extends ve{apiBaseUrl;accessToken;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??Zr,this.accessToken=e.accessToken??"d6c45897b8f6"}getProviderName(){return"debridge"}hasIntegratedApproval(){return!1}async getQuote(e){let{srcChainId:t,srcTokenAddress:n,srcTokenAmount:r,dstChainId:o,dstTokenAddress:s,recipientAddress:a,senderAddress:i,slippage:c,isCrossChain:u}=e,d=u??t!==o,p=await ct(this.getProviderName(),t);try{if(d){let f={srcChainId:String(t),srcChainTokenIn:n,srcChainTokenInAmount:r,dstChainId:String(o),dstChainTokenOut:s,dstChainTokenOutRecipient:a,srcChainOrderAuthorityAddress:i,dstChainOrderAuthorityAddress:a,accesstoken:this.accessToken};p&&(f.affiliateFeeRecipient=p.affiliateFeeRecipient,f.affiliateFeePercent=p.affiliateFeePercent);let g=await this.get(`${this.apiBaseUrl}/dln/order/create-tx`,f);return g.error||g.errorMessage?{success:!1,error:g.error??g.errorMessage,errorMessage:g.errorMessage??"deBridge quote failed"}:{success:!0,provider:"debridge",tx:g.tx,estimation:g.estimation,isCrossChain:!0,srcChainId:t,dstChainId:o,raw:g}}let m={chainId:String(t),tokenIn:n,tokenInAmount:r,tokenOut:s,tokenOutRecipient:a,accesstoken:this.accessToken};p&&(m.affiliateFeeRecipient=p.affiliateFeeRecipient,m.affiliateFeePercent=p.affiliateFeePercent),c!==void 0&&(m.slippage=c);let h=await this.get(`${this.apiBaseUrl}/chain/transaction`,m);return h.error||h.errorMessage?{success:!1,error:h.error??h.errorMessage,errorMessage:h.errorMessage??"deBridge quote failed"}:{success:!0,provider:"debridge",tx:h.tx,isCrossChain:!1,srcChainId:t,dstChainId:t,raw:h}}catch(m){return{success:!1,error:m,errorMessage:m instanceof Error?m.message:"Failed to fetch quote"}}}async checkApproval(e){let{chain:t,userAddress:n,tokenAddress:r,amount:o}=e,s=e.quoteData?.tx?.to;if(!s)return{isNeeded:!1,error:"No contract address found in quote"};if(!r||r.toLowerCase()===es)return{isNeeded:!1,integrated:!1,contractAddress:s,message:"Native coin needs no approval."};let a=await this.readAllowance(t,r,n,s);if(a===null)return{isNeeded:!0,integrated:!1,contractAddress:s,message:"Could not read allowance; approval required before the swap."};let i;try{i=BigInt(o)}catch{i=0n}return a>=i?{isNeeded:!1,integrated:!1,contractAddress:s,message:"Sufficient allowance already granted."}:{isNeeded:!0,integrated:!1,contractAddress:s,message:"Approval required: current allowance is below the swap amount."}}async readAllowance(e,t,n,r){if(!e)return null;let o=i=>i.toLowerCase().replace(/^0x/,"").padStart(64,"0"),s=`0xdd62ed3e${o(n)}${o(r)}`,a=await qn(e,t,s);if(typeof a!="string"||!/^0x[0-9a-fA-F]*$/.test(a)||a==="0x")return null;try{return BigInt(a)}catch{return null}}async executeSwap(e){let{quoteData:t}=e;return{success:!0,provider:"debridge",txData:t.tx??void 0,estimation:t.estimation}}async trackTransaction(e){let{requestIdOrTxHash:t}=e;try{let n=await this.get(`${this.apiBaseUrl}/dln/tx/${t}/order-ids`,{accesstoken:this.accessToken});if(!n.orderIds?.length)return{success:!1,status:"PENDING"};let r=n.orderIds[0];switch(((await this.get(`${this.apiBaseUrl}/dln/order/${r}/status`,{accesstoken:this.accessToken})).status??"").toUpperCase()){case"FULFILLED":case"SENTUNLOCK":case"CLAIMEDUNLOCK":return{success:!0,status:"COMPLETED"};case"ORDERCANCELLED":case"SENTORDERCANCEL":case"CLAIMEDORDERCANCEL":return{success:!0,status:"FAILED"};default:return{success:!0,status:"PENDING"}}}catch(n){return{success:!1,status:"ERROR",error:n instanceof Error?n.message:String(n)}}}async get(e,t){let n=Object.entries(t).map(([s,a])=>`${encodeURIComponent(s)}=${encodeURIComponent(String(a))}`).join("&"),r=n?`${e}?${n}`:e;return await(await fetch(r,{method:"GET",headers:{Accept:"application/json"}})).json()}};var ts="https://api.relay.link";function vo(l){return l===7565164||l==="7565164"?792703809:l}var mt=class extends ve{apiBaseUrl;apiKey;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??ts,this.apiKey=e.apiKey}getProviderName(){return"relay"}hasIntegratedApproval(){return!0}async getQuote(e){let{srcChainId:t,srcTokenAddress:n,srcTokenAmount:r,dstChainId:o,dstTokenAddress:s,recipientAddress:a,senderAddress:i,slippage:c}=e,u=await ct(this.getProviderName(),t),d={user:a||i,originChainId:vo(t),destinationChainId:vo(o),originCurrency:n,destinationCurrency:s,amount:r,recipient:a||i,tradeType:"EXACT_INPUT"};u&&(d.appFees=[{recipient:u.affiliateFeeRecipient,fee:Math.floor(u.affiliateFeePercent*100)}]),typeof c=="number"&&Number.isFinite(c)&&(d.slippageTolerance=Math.floor(c*100).toString());try{let p=await this.request("/quote/v2","POST",d);if(!p||p.error)return{success:!1,error:p?.error??"Unknown error",errorMessage:p?.message??"Failed to fetch quote from Relay"};let m=p.steps?.find(k=>k.id==="approve"),h=p.steps?.find(k=>k.id==="swap"||k.id==="deposit"),f=h?.items?.[0]?.data,g=Array.isArray(f?.instructions);return{success:!0,provider:"relay",tx:f?g?{data:{instructions:f.instructions??[],addressLookupTableAddresses:f.addressLookupTableAddresses??[]},chainId:f.chainId}:{from:f.from,to:f.to,data:f.data,value:f.value,chainId:f.chainId,maxFeePerGas:f.maxFeePerGas,maxPriorityFeePerGas:f.maxPriorityFeePerGas}:null,isCrossChain:t!==o,srcChainId:t,dstChainId:o,raw:{steps:p.steps,fees:p.fees,details:p.details,approveStep:m,requestId:h?.requestId}}}catch(p){return{success:!1,error:p,errorMessage:p instanceof Error?p.message:"Failed to fetch quote"}}}async checkApproval(e){let t=e.quoteData?.raw?.approveStep;if(t){let n=t.items?.[0]?.data;return{isNeeded:!0,integrated:!0,approvalData:n,contractAddress:n?.to,message:"Approval step is included in quote and will be executed first."}}return{isNeeded:!1,integrated:!0,message:"No approval needed or already approved"}}async executeSwap(e){let{quoteData:t}=e,n=t.raw;return n?.steps?{success:!0,provider:"relay",steps:n.steps,estimation:t.estimation}:{success:!1,error:"Invalid quote data"}}async trackTransaction(e){try{let n=(await this.request("/intents/status/v3","GET",{requestId:e.requestIdOrTxHash})).status??"";return n==="success"||n==="refunded"?{success:!0,status:"COMPLETED"}:n==="failure"||n==="expired"?{success:!0,status:"FAILED"}:{success:!0,status:"PENDING"}}catch(t){return{success:!1,status:"ERROR",error:t instanceof Error?t.message:String(t)}}}async request(e,t,n){let r={"Content-Type":"application/json"};this.apiKey&&(r["x-api-key"]=this.apiKey);let o=`${this.apiBaseUrl}${e}`,s={method:t,headers:r};if(t==="GET"&&n){let i=Object.entries(n).map(([c,u])=>`${encodeURIComponent(c)}=${encodeURIComponent(String(u))}`).join("&");o=`${o}?${i}`}else t==="POST"&&n&&(s.body=JSON.stringify(n));let a=await fetch(o,s);if(!a.ok){let i=await a.json().catch(()=>({}));throw new Error(i.message??`HTTP ${a.status}`)}return await a.json()}};var mn={debridge:{apiBaseUrl:"https://dln.debridge.finance/v1.0",accessToken:"d6c45897b8f6"},relay:{apiBaseUrl:"https://api.relay.link"}},Vt="debridge";function ns(l){if(l==null)return null;let e=typeof l=="number"?l:Number(l);return Number.isFinite(e)?e:null}var Ae=class{providerConfig;isProduction;cache=new Map;constructor(e=mn,t=!0){this.providerConfig=e,this.isProduction=t}async getService(e){let t=await this.getActiveProvider(e),n=this.cache.get(t);if(n)return n;let r=this.createService(t);return this.cache.set(t,r),r}async getServiceByProvider(e){let t=this.cache.get(e);if(t)return t;let n=this.createService(e);return this.cache.set(e,n),n}async getActiveProvider(e){let t=ns(e);return t===null?Vt:await dn(t,this.isProduction)??Vt}async isProviderActive(e,t){return await this.getActiveProvider(t)===e}getAvailableProviders(){return Object.keys(this.providerConfig)}clearCache(){this.cache.clear()}createService(e){let t=this.providerConfig[e]??{};switch(e){case"debridge":return new dt(t);case"relay":return new mt(t);default:{let n=e;throw new Error(`Unknown swap provider: ${String(n)}`)}}}},ce=new Ae;var os="https://api.coinpool.app/config/addresses",rs="https://coinpool-api-op.bacoor-test001.xyz/config/addresses",pn={prod:null,dev:null},Ht={prod:null,dev:null};async function ss(l){let e=l?"prod":"dev";if(pn[e])return pn[e];if(Ht[e])return Ht[e];let t=l?os:rs;return Ht[e]=(async()=>{try{let r=await(await fetch(t,{method:"GET",headers:{Accept:"application/json"}})).json();return pn[e]=r?.data??null,pn[e]}catch{return null}finally{Ht[e]=null}})(),Ht[e]}function as(l){let e=l.trim();if(/^0x[0-9a-fA-F]+$/.test(e)){let t=Number.parseInt(e,16);return Number.isFinite(t)?String(t):null}return/^\d+$/.test(e)?e:null}async function So(l,e,t){let n=await ss(t);if(!n)return;let r=as(e);if(!r)return;let o=n[l]?.[r];return Array.isArray(o)?o[0]:typeof o=="string"?o:void 0}async function hn(l,e){return So("createPoolProxyV3",l,e)}async function gn(l,e){return So("nftPositionUniswap",l,e)}var L="0x0000000000000000000000000000000000000000",is={"0x1":{hexId:"0x1",name:"Ethereum",defaultRpc:ne["0x1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa":{hexId:"0xa",name:"Optimism",defaultRpc:ne["0xa"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0x38":{hexId:"0x38",name:"BNB Smart Chain",defaultRpc:ne["0x38"],native:{symbol:"BNB",name:"BNB",decimals:18,coingeckoId:"binancecoin"},defaultMintGasLimit:12e5},"0x89":{hexId:"0x89",name:"Polygon",defaultRpc:ne["0x89"],native:{symbol:"MATIC",name:"Polygon",decimals:18,coingeckoId:"matic-network"},defaultMintGasLimit:12e5},"0x2105":{hexId:"0x2105",name:"Base",defaultRpc:ne["0x2105"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa4b1":{hexId:"0xa4b1",name:"Arbitrum One",defaultRpc:ne["0xa4b1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:22e5},"0xa86a":{hexId:"0xa86a",name:"Avalanche",defaultRpc:ne["0xa86a"],native:{symbol:"AVAX",name:"Avalanche",decimals:18,coingeckoId:"avalanche-2"},defaultMintGasLimit:12e5},"0xe708":{hexId:"0xe708",name:"Linea",defaultRpc:ne["0xe708"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5}};function z(l){return is[l.toLowerCase()]}function pt(l){return z(l)?.native}var xo="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";function Fn(l){let e=l.stableTicks??3,t=l.widePercent??{down:50,up:100},n=oe(l.currentTick,l.token0Decimals,l.token1Decimals),r=he(pe(l.currentTick-e*l.tickSpacing,l.tickSpacing,"down")),o=he(pe(l.currentTick+e*l.tickSpacing,l.tickSpacing,"up")),s=n*(1-t.down/100),a=n*(1+t.up/100),i=Pe(s,l.token0Decimals,l.token1Decimals),c=Pe(a,l.token0Decimals,l.token1Decimals),u=he(pe(i,l.tickSpacing,"down")),d=he(pe(c,l.tickSpacing,"up")),p=(m,h,f,g,y)=>{let k=oe(g,l.token0Decimals,l.token1Decimals),w=oe(y,l.token0Decimals,l.token1Decimals);return{id:m,label:h,description:f,tickLower:g,tickUpper:y,minPrice:k,maxPrice:w,minPercentChange:(k-n)/n*100,maxPercentChange:(w-n)/n*100}};return[p("stable","Stable","Good for stablecoins or low volatility pairs",r,o),p("wide","Wide","Good for volatile pairs",u,d)]}var ls=ge("slot0()"),cs=ge("liquidity()"),us=ge("tickSpacing()"),ds=ge("fee()"),ms=ge("token0()"),ps=ge("token1()"),hs=ge("decimals()"),_e=class{swapFactory;isProduction;constructor(e){this.isProduction=e?.isProduction??!0,this.swapFactory=e?.swapFactory??(this.isProduction?ce:new Ae(void 0,!1))}getRpcUrl(e){let t=ut(e);if(t)return t;let n=z(e);if(!n)throw new Error(`Unsupported chain: ${e}`);return n.defaultRpc}async getGatewayAddress(e){return hn(e,this.isProduction)}async getPositionManagerAddress(e){return gn(e,this.isProduction)}async ethCall(e,t,n){return fe(this.getRpcUrl(e),"eth_call",[{to:t,data:n},"latest"])}async getNativeBalance(e,t){let n=await fe(this.getRpcUrl(e),"eth_getBalance",[t,"latest"]);return BigInt(n)}async getGasPrice(e){let t=await fe(this.getRpcUrl(e),"eth_gasPrice",[]);return BigInt(t)}async getTransactionReceipt(e,t){return await fe(this.getRpcUrl(e),"eth_getTransactionReceipt",[t])}async extractMintedNftId(e,t){let n=await this.getPositionManagerAddress(e);if(!n)return null;let r=n.toLowerCase();for(let o of t.logs??[]){if((o.address??"").toLowerCase()!==r)continue;let s=o.topics??[];if(!(s.length<4||s[0]?.toLowerCase()!==xo||"0x"+s[1].slice(-40).toLowerCase()!==L))try{return BigInt(s[3]).toString()}catch{return null}}return null}async estimateGasReserveWei(e){let t=z(e);if(!t)throw new Error(`Unsupported chain: ${e}`);let n=await this.getGasPrice(e);return BigInt(t.defaultMintGasLimit)*n*18n/10n}async readPoolOnchain(e,t){let[n,r,o,s,a,i]=await Promise.all([this.ethCall(e,t,ls),this.ethCall(e,t,cs),this.ethCall(e,t,us),this.ethCall(e,t,ds),this.ethCall(e,t,ms),this.ethCall(e,t,ps)]),c=te(n),u=Ft(c,0),d=Bn(c,64),p=Ft(te(r),0),m=Number(Bn(te(o),0)),h=Number(Ft(te(s),0)),f=On(te(a),0),g=On(te(i),0),[y,k]=await Promise.all([this.readErc20Decimals(e,f),this.readErc20Decimals(e,g)]),w=fo(u,y,k);return{poolAddress:t,chainId:e,fee:h,tickSpacing:m,liquidity:p,sqrtPriceX96:u,currentTick:d,token0:f,token1:g,currentPrice:w}}async readErc20Decimals(e,t){if(t.toLowerCase()===L)return 18;let n=await this.ethCall(e,t,hs);return Number(Ft(te(n),0))}async getNativePriceUsd(e){let t=z(e);if(!t)return null;try{let n=`https://api.coingecko.com/api/v3/simple/price?ids=${t.native.coingeckoId}&vs_currencies=usd`,r=await fetch(n);if(!r.ok)return null;let s=(await r.json())[t.native.coingeckoId]?.usd;return typeof s=="number"?s:null}catch{return null}}splitNativeAmountForRange(e){let{nativeAmountWei:t,pool:n,tickLower:r,tickUpper:o,token0Decimals:s,token1Decimals:a}=e,i=2**96,c=Number(n.sqrtPriceX96)/i,u=Math.pow(1.0001,r/2),d=Math.pow(1.0001,o/2);if(c<=u)return{ratio0:1,ratio1:0,amountInFor0Wei:t,amountInFor1Wei:0n};if(c>=d)return{ratio0:0,ratio1:1,amountInFor0Wei:0n,amountInFor1Wei:t};let p=1/c-1/d,m=c-u,h=c*c,f=p*h,y=f+m;if(!Number.isFinite(y)||y<=0)throw new Error("splitNativeAmountForRange: invalid total value");let k=f/y,w=1-k,b=1000000000000000n,T=BigInt(Math.floor(k*Number(b))),P=t*T/b,x=t-P;return{ratio0:k,ratio1:w,amountInFor0Wei:P,amountInFor1Wei:x}}async fetchSwapLeg(e){let t=Number.parseInt(e.chainId,16);if(!Number.isFinite(t))throw new Error(`Invalid hex chain id: ${e.chainId}`);let r=await(await this.swapFactory.getService(t)).getQuote({srcChainId:t,srcTokenAddress:e.tokenIn,srcTokenAmount:e.tokenInAmount,dstChainId:t,dstTokenAddress:e.tokenOut,recipientAddress:e.tokenOutRecipient,senderAddress:e.senderAddress??e.tokenOutRecipient,slippage:e.slippage,isCrossChain:!1});if(!r.success||!r.tx)throw new Error(r.errorMessage??"Swap quote failed");let o=r.tx;if(!o.to||typeof o.data!="string")throw new Error("Swap quote returned an unsupported tx shape (non-EVM payload?)");let s=r.raw??{},a=s.tokenOut?.minAmount??s.details?.currencyOut?.minimumAmount??"0";return{to:o.to,data:o.data,value:o.value??"0",minAmountOut:a}}encodeMintCalldata(e){if(e.paymentInfo.length!==2||e.exchanges.length!==2)throw new Error("encodeMintCalldata: paymentInfo and exchanges must each have length 2");let t=ge("mint((address,uint256,address,uint256)[2],(address,bytes,uint256)[2],(uint24,uint256,uint256,int24,int24))"),n=e.paymentInfo.map(h=>cn(h.tokenIn)+re(h.tokenInAmount)+cn(h.tokenOut)+re(h.tokenOutAmount)).join(""),o=e.exchanges.map(h=>{let f=te(h.data||"0x"),g=f.length/2,y=re(g),k=(64-f.length%64)%64,w=f+"0".repeat(k);return{head:cn(h.to)+re(96)+re(h.value),body:y+w}}).map(h=>h.head+h.body),s=o.length*32,a=[];for(let h of o)a.push(re(s)),s+=h.length/2;let i=a.join("")+o.join(""),c=re(e.fee,24)+re(e.amount0Min)+re(e.amount1Min)+Mn(e.tickLower)+Mn(e.tickUpper),p=256+32+160,m=n+re(p)+c+i;return t+m}_keccak(e){return yo(e)}};var gs=/^0x[a-fA-F0-9]{40}$/,ht=class extends v{name="open-add-liquidity-form";kind="ui";category="pool-action";noSuggestions=!0;description='Open the Add-Liquidity form for a specific Uniswap V3 pool. Use this when the user wants to provide liquidity / add LP / farm / stake / deposit into a pool \u2014 natural-language verbs in any language all map here. Resolves the pool by token symbols (and optional fee tier), reads on-chain state, checks the connected wallet\'s NATIVE balance, then returns a UI payload that the FE renders as the form (with two preset ranges: Stable \xB13 ticks and Wide -50%/+100%). CONSTRAINT: the gateway only accepts the chain\'s NATIVE coin as input (ETH on Ethereum/Base/Arbitrum/Optimism/Linea, BNB on BSC, MATIC on Polygon, AVAX on Avalanche). The native amount is auto-split via deBridge into both pool tokens \u2014 the user never selects a different input. If the user asks to add liquidity using a NON-NATIVE token amount (e.g. "add pool with 5 USDT", "th\xEAm 100 USDC"), pass that symbol via `requestedNonNativeToken` so the tool can return error="unsupported_input_token" \u2014 never silently convert their non-native amount into native or USD. PRECONDITION: a connected wallet (userContext.walletAddress). If absent, the tool returns error="wallet_not_connected" and the agent must ask the user to connect.';parameters=[{name:"token0Symbol",type:"string",description:`Symbol of one of the pool's tokens (e.g. "USDC"). Pair order does not matter.`,required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Required to uniquely identify the pool.',required:!0},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Omit when unknown \u2014 the tool will resolve the best match automatically.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"prefillNativeAmount",type:"number",description:"Optional NATIVE-COIN amount (human-readable) to pre-fill the form's input field. ONLY pass this when the user named an amount in the chain's native coin itself \u2014 ETH on Ethereum/Base/Arbitrum/Optimism/Linea, BNB on BSC, MATIC on Polygon, AVAX on Avalanche. Example: \"add 0.1 ETH to USDC/WETH on Base\". Pass the plain decimal number (e.g. 0.1 for 0.1 ETH). DO NOT convert a non-native amount (USDC, USDT, DAI, WBTC, \u2026) into native here \u2014 pass `requestedNonNativeToken` instead so the tool can reject. Mutually exclusive with prefillUsdAmount \u2014 pass one or neither.",required:!1},{name:"prefillUsdAmount",type:"number",description:'Optional USD amount to pre-fill the form\'s input field. Use this ONLY when the user explicitly speaks in USD/dollars \u2014 e.g. "add $10 to this pool", "10 dollars", "10 \u0111\xF4". The tool converts the USD value to native amount automatically using the current native-token price. DO NOT use this as a workaround for non-native token amounts (e.g. "5 USDT" is NOT $5 \u2014 USDT may de-peg, and even if 1:1 the user asked for a token, not USD). For non-native token amounts pass `requestedNonNativeToken` instead. Mutually exclusive with prefillNativeAmount \u2014 pass one or neither.',required:!1},{name:"requestedNonNativeToken",type:"string",description:`Set this ONLY when the user named an AMOUNT in a non-native token \u2014 e.g. "add pool with 5 USDT", "add 100 USDC to USDC/WETH", "th\xEAm 5 USDT v\xE0o pool". The trigger is "<number> <non-native-symbol>" in the user's message, NOT the mere appearance of the symbol. Pass the symbol the user attached the amount to (e.g. "USDT", "USDC", "DAI"). CRITICAL: If the user also names a NATIVE amount in the same message (e.g. "add USDC/WETH with 0.1 ETH", "th\xEAm pool USDC/WETH 0.3% v\u1EDBi 0.000005 ETH"), the native amount is the input \u2014 leave this EMPTY and use prefillNativeAmount. The pool pair symbols (the two tokens after "pool" / "to") are NEVER the input token by themselves \u2014 they only identify which pool. The tool will return error="unsupported_input_token" so the agent can tell the user that only the chain's native coin is accepted as input. DO NOT silently convert the user's non-native amount into a native amount or USD amount \u2014 the user explicitly named a different asset and must be informed, not auto-corrected. Leave empty when the user did name a native amount, a USD amount, or no amount at all.`,required:!1},{name:"rangeStrategy",type:"string",description:'Optional preset that pre-fills the min/max price inputs (FE keeps them editable). One of: "stable" \u2014 \xB13 tickSpacing around the current price (narrow, higher yield, higher out-of-range risk; pick for stablecoin / low-volatility pairs and when the user says "safe", "tight", "high yield", "t\u1EADp trung"); "wide" \u2014 currentPrice \xD7 [0.5, 2.0] (broad, lower yield, less rebalancing; pick when the user says "wide", "set and forget", "r\u1ED9ng"); "full" \u2014 full V3 tick range (V2-style, lowest yield, never goes out of range; pick when the user says "full range", "v2-style", "kh\xF4ng lo out of range"). Omit when the user did not signal a preference AND did not provide explicit minPrice/maxPrice. Mutually exclusive with minPrice/maxPrice \u2014 if both are passed, explicit prices win.',required:!1},{name:"minPrice",type:"number",description:'Optional explicit lower bound for the price range, in token1-per-token0 units (same unit as pool.currentPrice \u2014 decimal-adjusted). Pass when the user named a concrete number, e.g. "range 1800 to 2200" \u2192 minPrice=1800. Must be paired with maxPrice. When set, overrides rangeStrategy.',required:!1},{name:"maxPrice",type:"number",description:'Optional explicit upper bound for the price range, in token1-per-token0 units (same unit as pool.currentPrice \u2014 decimal-adjusted). Pass when the user named a concrete number, e.g. "range 1800 to 2200" \u2192 maxPrice=2200. Must be paired with minPrice. When set, overrides rangeStrategy.',required:!1}];uniswap;pool;minProvideUsd;constructor(e){super(),this.uniswap=new Q(e),this.pool=new _e(e?.pool),this.minProvideUsd=e?.minProvideUsd??.01}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=R(e.chain,t),r=typeof e.token0Symbol=="string"?e.token0Symbol.trim():"",o=typeof e.token1Symbol=="string"?e.token1Symbol.trim():"",s=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0,a=typeof e.prefillNativeAmount=="number"&&Number.isFinite(e.prefillNativeAmount)&&e.prefillNativeAmount>0?e.prefillNativeAmount:null,i=typeof e.prefillUsdAmount=="number"&&Number.isFinite(e.prefillUsdAmount)&&e.prefillUsdAmount>0?e.prefillUsdAmount:null,c=typeof e.requestedNonNativeToken=="string"&&e.requestedNonNativeToken.trim()?e.requestedNonNativeToken.trim():null,u=c&&a!=null&&(c.toLowerCase()===r.toLowerCase()||c.toLowerCase()===o.toLowerCase())?null:c,d=this.parseRangeStrategy(e.rangeStrategy),p=typeof e.minPrice=="number"&&Number.isFinite(e.minPrice)&&e.minPrice>0?e.minPrice:null,m=typeof e.maxPrice=="number"&&Number.isFinite(e.maxPrice)&&e.maxPrice>0?e.maxPrice:null;if(!r||!o)return{error:"missing_pool_identifier",_instructions:"token0Symbol and token1Symbol are required. Ask the user for the missing token symbol."};let h=z(n);if(!h)return{error:"unsupported_chain",chain:n,_instructions:`Chain ${n} is not supported by the add-liquidity flow.`};let f=pt(n);if(u&&f&&u.toLowerCase()!==f.symbol.toLowerCase())return{error:"unsupported_input_token",requestedToken:u,nativeSymbol:f.symbol,chainName:h.name,_instructions:`The user asked to add liquidity using ${u}, but this app only supports the chain's native coin (${f.symbol} on ${h.name}) as the input. Tell the user this in their language and ask them to either restate the amount in ${f.symbol} or in USD. DO NOT silently convert their ${u} amount, do NOT pre-fill the form, and do NOT call this tool again until the user provides a native or USD amount.`};let g=await this.pool.getGatewayAddress(n);if(!g)return{error:"no_gateway_configured",chain:n,_instructions:`No gateway contract is configured for chain ${n} (${h.name}). Tell the user this chain is not yet supported for add-liquidity in this app.`};let y=t?.walletAddress;if(!y)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before adding liquidity. Do NOT render the form."};let k=await this.uniswap.searchPools({query:`${r} ${o}`,chain:n,page:1,size:100,protocolVersion:"V3",feeTier:s});if(!k.success)return{error:k.error||"pool_search_failed"};let w=k.data??[],b=w.find(M=>this.matchesPairExact(M,r,o,s)),T=b?[]:w.filter(M=>this.matchesPair(M,r,o,s));if(!b&&T.length===0)return{error:"pool_not_found",_instructions:`No V3 pool found for ${r}/${o}${s!=null?` at ${s}bps`:""} on ${h.name}. Verify the inputs with the user.`};if(!b&&T.length>1)return{candidates:T.map(M=>({address:M.id,token0Symbol:M.token0?.symbol,token1Symbol:M.token1?.symbol,feeTier:M.feeTier,feeTierPercent:M.feeTier!=null?M.feeTier/1e4:void 0,volume24hUsd:M.volume1DayUsd??M.volumeUsd24hr,tvlUsd:M.tvlUsd,...M.apr?{apr:M.apr}:void 0})),_instructions:"Multiple pools matched. Present them as a numbered list (pair, fee %, TVL, 24h volume, address, apr) and ask the user to pick one. Then re-call this tool with the chosen pool's exact token0Symbol, token1Symbol, and feeTier."};let P=b??T[0];if(!P.id||!gs.test(P.id))return{error:"invalid_pool_address"};let x=await this.pool.readPoolOnchain(n,P.id),[A,I,S]=await Promise.all([this.pool.getNativeBalance(n,y),this.pool.estimateGasReserveWei(n),this.pool.getNativePriceUsd(n)]),_=A>I?A-I:0n,C=pt(n),q=Number(_)/10**C.decimals,K=Number(A)/10**C.decimals,H=S!=null?q*S:null,[ae,G]=await Promise.all([this.pool.readErc20Decimals(n,x.token0),this.pool.readErc20Decimals(n,x.token1)]),be={min:oe(it,ae,G),max:oe(lt,ae,G)},we=null;A===0n?we="no_balance":H!=null&&H<this.minProvideUsd&&(we="insufficient_balance");let ke=P.token0,Ce=P.token1,_n=[25,50,75,100].map(M=>({percent:M,amountWei:(_*BigInt(M)/100n).toString(),amount:q*(M/100)})),Ue=null,ie=null;if(a!=null){ie=a;let M=BigInt(Math.floor(ie*10**C.decimals));M>0n&&(Ue=M.toString())}else if(i!=null&&S!=null&&S>0){ie=i/S;let M=BigInt(Math.floor(ie*10**C.decimals));M>0n&&(Ue=M.toString())}let Re={address:L,isNative:!0,symbol:C.symbol,name:C.name,decimals:C.decimals,priceUsd:S,balanceWei:A.toString(),balance:K,gasReserveWei:I.toString(),spendableWei:_.toString(),spendable:q,spendableUsd:H,quickRates:_n,minProvideUsd:this.minProvideUsd,warning:we,warningMessage:we==="no_balance"?`You have no ${C.symbol} on ${h.name}. Top up to add liquidity.`:we==="insufficient_balance"?`Spendable ${C.symbol} is below the $${this.minProvideUsd} minimum.`:null,prefillAmount:ie,prefillAmountWei:Ue},Vn=this.computePrefillRange({explicitMinPrice:p,explicitMaxPrice:m,rangeStrategy:d,currentTick:x.currentTick,tickSpacing:x.tickSpacing,token0Decimals:ae,token1Decimals:G,priceBounds:be});return{ui:{component:"AddLiquidityForm",props:{chain:{hexId:n,name:h.name},pool:{address:x.poolAddress,fee:x.fee,feePercent:x.fee/1e4,tickSpacing:x.tickSpacing,currentTick:x.currentTick,currentPrice:x.currentPrice,sqrtPriceX96:x.sqrtPriceX96.toString(),token0:{address:x.token0,symbol:ke?.symbol,name:ke?.name,logo:ke?.logo},token1:{address:x.token1,symbol:Ce?.symbol,name:Ce?.name,logo:Ce?.logo}},inputToken:Re,priceBounds:be,prefillRange:Vn,gatewayAddress:g}},summary:this.buildSummary({chainName:h.name,pair:`${ke?.symbol??r}/${Ce?.symbol??o}`,feePercent:x.fee/1e4,warning:we,minProvideUsd:this.minProvideUsd,nativeSymbol:C.symbol}),_instructions:`The add-liquidity action is ready. In the user's language, invite them to enter the amount and price range to add liquidity, naming the pool + chain (e.g. "Enter the amount and price range to add liquidity to <pool> on <chain>"). If warning="no_balance" tell them they need to top up. If warning="insufficient_balance" tell them the balance is below the minimum. Do NOT mention balance amounts, deBridge, native coin mechanics, UI, forms, or internal tool names. Wait for the user to fill in amount + price range before proceeding.`}}matchesPairExact(e,t,n,r){if(!r||r!=null&&e.feeTier!==r)return!1;let o=(e.token0?.symbol??"").toLowerCase(),s=(e.token1?.symbol??"").toLowerCase(),a=t.toLowerCase(),i=n.toLowerCase();return o===a&&s===i||o===i&&s===a}matchesPair(e,t,n,r){if(r!=null&&e.feeTier!==r)return!1;let o=(e.token0?.symbol??"").toLowerCase(),s=(e.token1?.symbol??"").toLowerCase(),a=t.toLowerCase(),i=n.toLowerCase(),c=this.symMatch(o,a)&&this.symMatch(s,i),u=this.symMatch(o,i)&&this.symMatch(s,a);return c||u}symMatch(e,t){return!e||!t?!1:e.includes(t)||t.includes(e)}parseRangeStrategy(e){if(typeof e!="string")return null;let t=e.trim().toLowerCase();return t==="stable"||t==="wide"||t==="full"?t:null}computePrefillRange(e){let{explicitMinPrice:t,explicitMaxPrice:n,rangeStrategy:r,priceBounds:o}=e;if(t!=null&&n!=null&&n>t)return{strategy:"user",minPrice:Math.max(t,o.min),maxPrice:Math.min(n,o.max)};if(r==="full")return{strategy:"full",minPrice:o.min,maxPrice:o.max};if(r==="stable"||r==="wide"){let a=Fn({currentTick:e.currentTick,tickSpacing:e.tickSpacing,token0Decimals:e.token0Decimals,token1Decimals:e.token1Decimals}).find(i=>i.id===r);if(a)return{strategy:r,minPrice:a.minPrice,maxPrice:a.maxPrice}}return null}buildSummary(e){let t=[`Add-liquidity form opened for ${e.pair} ${e.feePercent}% on ${e.chainName}.`];return e.warning==="no_balance"?t.push(`No ${e.nativeSymbol} balance \u2014 top up the wallet to add liquidity.`):e.warning==="insufficient_balance"&&t.push(`Balance is below the $${e.minProvideUsd} minimum \u2014 top up before submitting.`),t.join(" ")}};var Po=[{type:"constructor",inputs:[{name:"owner_",type:"address",internalType:"address"},{name:"supportedRouter_",type:"address",internalType:"address"},{name:"nonfungiblePositionManager_",type:"address",internalType:"address"}],stateMutability:"nonpayable"},{type:"function",name:"NATIVE_TOKEN",inputs:[],outputs:[{name:"",type:"address",internalType:"address"}],stateMutability:"view"},{type:"function",name:"getSupportedRouters",inputs:[],outputs:[{name:"",type:"address[]",internalType:"address[]"}],stateMutability:"view"},{type:"function",name:"increaseLiquidity",inputs:[{name:"tokenId_",type:"uint256",internalType:"uint256"},{name:"swapInfo_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapInfo[2]",components:[{name:"tokenIn",type:"address",internalType:"address"},{name:"tokenInAmount",type:"uint256",internalType:"uint256"},{name:"tokenOut",type:"address",internalType:"address"},{name:"tokenOutAmount",type:"uint256",internalType:"uint256"}]},{name:"swapOp_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapOp[2]",components:[{name:"to",type:"address",internalType:"address"},{name:"value",type:"uint256",internalType:"uint256"},{name:"data",type:"bytes",internalType:"bytes"}]},{name:"increaseLiquidityData_",type:"tuple",internalType:"struct CoinPoolGateway.IncreaseLiquidityData",components:[{name:"amount0Min",type:"uint256",internalType:"uint256"},{name:"amount1Min",type:"uint256",internalType:"uint256"}]}],outputs:[],stateMutability:"payable"},{type:"function",name:"mint",inputs:[{name:"swapInfo_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapInfo[2]",components:[{name:"tokenIn",type:"address",internalType:"address"},{name:"tokenInAmount",type:"uint256",internalType:"uint256"},{name:"tokenOut",type:"address",internalType:"address"},{name:"tokenOutAmount",type:"uint256",internalType:"uint256"}]},{name:"swapOp_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapOp[2]",components:[{name:"to",type:"address",internalType:"address"},{name:"value",type:"uint256",internalType:"uint256"},{name:"data",type:"bytes",internalType:"bytes"}]},{name:"mintData_",type:"tuple",internalType:"struct CoinPoolGateway.MintData",components:[{name:"fee",type:"uint24",internalType:"uint24"},{name:"tickLower",type:"int24",internalType:"int24"},{name:"tickUpper",type:"int24",internalType:"int24"},{name:"amount0Min",type:"uint256",internalType:"uint256"},{name:"amount1Min",type:"uint256",internalType:"uint256"}]}],outputs:[],stateMutability:"payable"},{type:"function",name:"nonfungiblePositionManager",inputs:[],outputs:[{name:"",type:"address",internalType:"contract INonfungiblePositionManager"}],stateMutability:"view"},{type:"function",name:"owner",inputs:[],outputs:[{name:"",type:"address",internalType:"address"}],stateMutability:"view"},{type:"function",name:"pause",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"paused",inputs:[],outputs:[{name:"",type:"bool",internalType:"bool"}],stateMutability:"view"},{type:"function",name:"renounceOwnership",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"rescue",inputs:[{name:"token_",type:"address",internalType:"address"},{name:"amount_",type:"uint256",internalType:"uint256"},{name:"recipient_",type:"address",internalType:"address"}],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"transferOwnership",inputs:[{name:"newOwner",type:"address",internalType:"address"}],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"unpause",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"updateSupportedRouters",inputs:[{name:"routers_",type:"address[]",internalType:"address[]"},{name:"isSupported_",type:"bool[]",internalType:"bool[]"}],outputs:[],stateMutability:"nonpayable"},{type:"event",name:"LiquidityIncreased",inputs:[{name:"tokenId",type:"uint256",indexed:!1,internalType:"uint256"},{name:"token0",type:"address",indexed:!1,internalType:"address"},{name:"token1",type:"address",indexed:!1,internalType:"address"},{name:"amount0LiquidityIncreased",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1LiquidityIncreased",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0IncreaseLiquidityRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1IncreaseLiquidityRefunded",type:"uint256",indexed:!1,internalType:"uint256"}],anonymous:!1},{type:"event",name:"Minted",inputs:[{name:"tokenId",type:"uint256",indexed:!1,internalType:"uint256"},{name:"token0",type:"address",indexed:!1,internalType:"address"},{name:"token1",type:"address",indexed:!1,internalType:"address"},{name:"amount0Minted",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1Minted",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0MintRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1MintRefunded",type:"uint256",indexed:!1,internalType:"uint256"}],anonymous:!1},{type:"event",name:"OwnershipTransferred",inputs:[{name:"previousOwner",type:"address",indexed:!0,internalType:"address"},{name:"newOwner",type:"address",indexed:!0,internalType:"address"}],anonymous:!1},{type:"event",name:"Paused",inputs:[{name:"account",type:"address",indexed:!1,internalType:"address"}],anonymous:!1},{type:"event",name:"SupportedRoutersUpdated",inputs:[{name:"routers",type:"address[]",indexed:!1,internalType:"address[]"},{name:"isSupported",type:"bool[]",indexed:!1,internalType:"bool[]"}],anonymous:!1},{type:"event",name:"Unpaused",inputs:[{name:"account",type:"address",indexed:!1,internalType:"address"}],anonymous:!1},{type:"error",name:"AddressEmptyCode",inputs:[{name:"target",type:"address",internalType:"address"}]},{type:"error",name:"EnforcedPause",inputs:[]},{type:"error",name:"ExpectedPause",inputs:[]},{type:"error",name:"FailedCall",inputs:[]},{type:"error",name:"InsufficientAmountOut",inputs:[]},{type:"error",name:"InsufficientBalance",inputs:[{name:"balance",type:"uint256",internalType:"uint256"},{name:"needed",type:"uint256",internalType:"uint256"}]},{type:"error",name:"InvalidAmountIn",inputs:[]},{type:"error",name:"InvalidLength",inputs:[]},{type:"error",name:"InvalidRefundUnusedToken",inputs:[]},{type:"error",name:"InvalidSwapAction",inputs:[]},{type:"error",name:"InvalidTokenInInfo",inputs:[]},{type:"error",name:"InvalidTokenOutInfo",inputs:[]},{type:"error",name:"InvalidTotalAmountIn",inputs:[]},{type:"error",name:"NotSupportedRouter",inputs:[]},{type:"error",name:"OwnableInvalidOwner",inputs:[{name:"owner",type:"address",internalType:"address"}]},{type:"error",name:"OwnableUnauthorizedAccount",inputs:[{name:"account",type:"address",internalType:"address"}]},{type:"error",name:"ReentrancyGuardReentrantCall",inputs:[]},{type:"error",name:"SafeERC20FailedDecreaseAllowance",inputs:[{name:"spender",type:"address",internalType:"address"},{name:"currentAllowance",type:"uint256",internalType:"uint256"},{name:"requestedDecrease",type:"uint256",internalType:"uint256"}]},{type:"error",name:"SafeERC20FailedOperation",inputs:[{name:"token",type:"address",internalType:"address"}]}],fn=[{type:"function",name:"approve",stateMutability:"nonpayable",inputs:[{name:"spender",type:"address"},{name:"amount",type:"uint256"}],outputs:[{name:"",type:"bool"}]},{type:"function",name:"allowance",stateMutability:"view",inputs:[{name:"owner",type:"address"},{name:"spender",type:"address"}],outputs:[{name:"",type:"uint256"}]}];var gt=class extends v{name="preview-add-liquidity";kind="action";category="pool-action";noSuggestions=!0;description="Preview the add-liquidity transaction once the user has filled the AddLiquidityForm with a native amount and a price range (min/max price in token1 per token0). The tool converts prices \u2192 ticks (rounded to tickSpacing), fetches deBridge swap quotes for both legs, encodes the gateway.mint call, and returns an unsigned tx the FE will sign. Do NOT call this until the user has filled both prices and an amount.";parameters=[{name:"poolAddress",type:"string",description:"V3 pool contract address (returned by open-add-liquidity-form in props.pool.address).",required:!0},{name:"chain",type:"string",description:`Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea .Must match the pool's chain. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.`,required:!1},{name:"minPrice",type:"number",description:'Lower bound of the position price range, in token1-per-token0 units (decimal-adjusted \u2014 the SAME unit as pool.currentPrice from open-add-liquidity-form). Example: if currentPrice is 1800 (USDC per WETH), minPrice 900 means "the position is in-range while WETH \u2265 900 USDC". Must be > 0 and < maxPrice.',required:!0},{name:"maxPrice",type:"number",description:'Upper bound of the position price range, in the same units as minPrice. Must be > minPrice. Pass a very large number (or props.priceBounds.max) for a "full range upper" position.',required:!0},{name:"nativeAmount",type:"number",description:"Native amount the user wants to spend, as a human-readable decimal number (e.g. 0.1 for 0.1 ETH). The tool converts to wei internally.",required:!0},{name:"slippageBps",type:"number",description:"Slippage tolerance in basis points (100 = 1%). Optional \u2014 defaults to 100.",required:!1}];pool;defaultSlippageBps;mintMinPercent;pantograph;constructor(e){super(),this.pool=new _e(e?.pool),this.defaultSlippageBps=e?.defaultSlippageBps??"auto",this.mintMinPercent=e?.mintMinPercent??80,this.pantograph=new Y}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=typeof e.poolAddress=="string"?e.poolAddress.trim():"",r=R(e.chain,t,""),o=Number(e.minPrice),s=Number(e.maxPrice),a=typeof e.nativeAmount=="number"&&Number.isFinite(e.nativeAmount)?e.nativeAmount:NaN,i=typeof e.slippageBps=="number"&&Number.isFinite(e.slippageBps)&&String(e.slippageBps)!=="auto"?Math.floor(e.slippageBps)/100:String(this.defaultSlippageBps);if(!n||!r||!Number.isFinite(a)||!Number.isFinite(o)||!Number.isFinite(s))return{error:"missing_inputs",_instructions:"poolAddress, chain, minPrice, maxPrice, nativeAmount are all required."};if(o<=0||s<=0||o>=s)return{error:"invalid_price_range",_instructions:"minPrice and maxPrice must both be > 0 and minPrice must be strictly less than maxPrice."};if(a<=0)return{error:"invalid_native_amount",_instructions:"nativeAmount must be > 0."};let c=z(r);if(!c)return{error:"unsupported_chain",chain:r};let u=await this.pool.getGatewayAddress(r);if(!u)return{error:"no_gateway_configured",chain:r};let d=t?.walletAddress;if(!d)return{error:"wallet_not_connected"};let p=pt(r),m=BigInt(Math.floor(a*10**p.decimals));if(m<=0n)return{error:"invalid_native_amount",_instructions:"nativeAmount must be > 0."};let h=await this.pool.readPoolOnchain(r,n),[f,g]=await Promise.all([this.pool.readErc20Decimals(r,h.token0),this.pool.readErc20Decimals(r,h.token1)]),y=Pe(o,f,g),k=Pe(s,f,g),w=he(pe(y,h.tickSpacing,"down")),b=he(pe(k,h.tickSpacing,"up"));if(w>=b||w<it||b>lt)return{error:"invalid_range_after_alignment",_instructions:"After aligning to tickSpacing the range collapsed. Ask the user for a wider price range.",rawTickLower:y,rawTickUpper:k,tickLower:w,tickUpper:b};let T={tickLower:w,tickUpper:b,minPrice:oe(w,f,g),maxPrice:oe(b,f,g)},[P,x,A]=await Promise.all([this.pool.getNativeBalance(r,d),this.pool.estimateGasReserveWei(r),this.pool.getNativePriceUsd(r)]),I=m+x;if(P<I)return{error:"insufficient_balance",_instructions:"User does not have enough native coin (amount + gas reserve). Tell them the shortfall in their language.",balanceWei:P.toString(),requiredWei:I.toString(),gasReserveWei:x.toString()};let S=this.pool.splitNativeAmountForRange({nativeAmountWei:m,pool:h,tickLower:w,tickUpper:b,token0Decimals:f,token1Decimals:g}),_=h.token0.toLowerCase()===L,C=h.token1.toLowerCase()===L,q=[];if(_||S.amountInFor0Wei===0n)q.push({tokenIn:L,tokenOut:h.token0,amountIn:S.amountInFor0Wei,tx:{to:L,data:"0x",value:"0"},minAmountOut:_?S.amountInFor0Wei:0n});else{let W=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:L,tokenInAmount:S.amountInFor0Wei.toString(),tokenOut:h.token0,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});q.push({tokenIn:L,tokenOut:h.token0,amountIn:S.amountInFor0Wei,tx:{to:W.to,data:W.data,value:W.value},minAmountOut:BigInt(W.minAmountOut||"0")})}if(C||S.amountInFor1Wei===0n)q.push({tokenIn:L,tokenOut:h.token1,amountIn:S.amountInFor1Wei,tx:{to:L,data:"0x",value:"0"},minAmountOut:C?S.amountInFor1Wei:0n});else{let W=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:L,tokenInAmount:S.amountInFor1Wei.toString(),tokenOut:h.token1,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});q.push({tokenIn:L,tokenOut:h.token1,amountIn:S.amountInFor1Wei,tx:{to:W.to,data:W.data,value:W.value},minAmountOut:BigInt(W.minAmountOut||"0")})}let K=W=>W*BigInt(this.mintMinPercent)/100n,H=K(q[0].minAmountOut),ae=K(q[1].minAmountOut),G=q.map(W=>({tokenIn:W.tokenIn,tokenInAmount:BigInt(W.amountIn),tokenOut:W.tokenOut,tokenOutAmount:BigInt(W.minAmountOut)})),be=q.map(W=>({to:W.tx.to,data:W.tx.data,value:BigInt(W.tx.value||"0")}));console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ paymentInfo:",G),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ exchangesInfo:",be),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ args:",{args:[G,be,{fee:Number(h.fee),amount0Min:BigInt(H),amount1Min:BigInt(ae),tickLower:w,tickUpper:b}]});let we=De({abi:Po,functionName:"mint",args:[G,be,{fee:Number(h.fee),amount0Min:BigInt(H),amount1Min:BigInt(ae),tickLower:w,tickUpper:b}]}),ke=a,Ce={chainId:r,to:u,data:we,value:m.toString(),from:d},_n={nativeIn:ke,nativeInUsd:A!=null?ke*A:null,ratio:{token0Percent:S.ratio0,token1Percent:S.ratio1},expectedToken0:Number(q[0].minAmountOut)/10**f,expectedToken1:Number(q[1].minAmountOut)/10**g,amount0Min:H.toString(),amount1Min:ae.toString(),slippageBps:i},Ue=await this.pantograph.getTokensMetadata([h.token0,h.token1],r),ie=Ue[h.token0],Re=Ue[h.token1];return{ui:{component:"ConfirmAddLiquidityTx",props:{chain:{hexId:r,name:c.name},unsignedTx:Ce,summary:_n,pool:{...h,feePercent:h.fee/1e4,token0:{address:h.token0,symbol:ie.symbol,name:ie.name,decimals:ie.decimals,logo:ie.icon_image},token1:{address:h.token1,symbol:Re.symbol,name:Re.name,decimals:Re.decimals,logo:Re.icon_image}},range:T,gatewayAddress:u}},summary:`Ready to add ${ke} ${p.symbol} to pool ${h.token0}/${h.token1} ${h.fee/1e4}% on ${c.name}, range [${T.minPrice}, ${T.maxPrice}].`,_instructions:"A confirmation panel has been opened on the FE; the user will sign and broadcast the unsigned tx with their wallet. Briefly tell them (in their language) what is about to happen \u2014 the native amount, the auto-split ratio between the two pool tokens, and the chosen price range. Do NOT mention internal tool/component names."}}};var Ao=365,fs=1e3,ys=/^0x[a-fA-F0-9]{40}$/;function bs(l,e){return{depositUsd:l,aprPercent:e*100,dailyUsd:l*e/Ao,weeklyUsd:l*e/52,monthlyUsd:l*e/12,yearlyUsd:l*e}}var ft=class extends v{name="estimate_pool_yield";description=`Estimate daily / weekly / monthly / yearly fee yield for a USD deposit into a Uniswap V3 pool. Answers questions like: "If I put $1000 into the USDC/WETH 0.05% pool, how much per day?", "b\u1ECF 500$ v\xE0o pool ETH/USDC 0.3% m\u1ED7i ng\xE0y l\u1EDDi bao nhi\xEAu?", "APR of WBTC/ETH 0.3% on Arbitrum". BEHAVIOR (designed to minimize back-and-forth with the user):
|
|
106
|
+
\u2022 If only ONE token symbol is given \u2192 auto-pick the highest-volume V3 pool containing that token and compute yield. Do NOT ask the user for the other token.
|
|
107
|
+
\u2022 If BOTH tokens are given but fee tier is missing AND multiple fee tiers exist \u2192 return candidates (one per fee tier) so the agent can ask the user to pick.
|
|
108
|
+
\u2022 If depositUsd is missing \u2192 default to $1000 and clearly mark the result as a per-$1000 example. Do NOT ask the user for the deposit amount.
|
|
109
|
+
\u2022 If chain is missing \u2192 default to userContext.chain or "0x1" (Ethereum).
|
|
110
|
+
Source: Uniswap ExploreStats (live 24h data). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"token0Symbol",type:"string",description:'Symbol of one of the pool tokens (e.g. "USDC"). Pair order does not matter.',required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Optional \u2014 if omitted, the tool auto-picks the highest-volume pool containing token0Symbol.',required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Optional \u2014 if omitted and the pair has multiple fee tiers, the tool returns candidates.",required:!1},{name:"depositUsd",type:"number",description:"USD amount to deposit. Optional \u2014 defaults to 1000 and the response is marked as a per-$1000 example.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new Q(e)}async run(e,t){let n=R(e.chain,t),r=typeof e.token0Symbol=="string"?e.token0Symbol.trim():"",o=typeof e.token1Symbol=="string"?e.token1Symbol.trim():"",s=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0,a=typeof e.depositUsd=="number"&&Number.isFinite(e.depositUsd)&&e.depositUsd>0,i=a?e.depositUsd:fs;if(!r)return{error:"token0Symbol is required. Ask the user which pool / token they want to estimate yield for."};let c=o?`${r} ${o}`:r,u=await this.service.searchPools({query:c,chain:n,page:1,size:50,protocolVersion:"V3"});if(!u.success)return{error:u.error||"Pool search failed"};let d=u.data??[],p=this.filterPools(d,r,o,s);if(p.length===0)return{error:`No V3 pool found for ${this.describeQuery(r,o,s)} on chain ${n}. Verify the token symbols with the user.`};let m=this.rankByVolume(p);if(o&&s==null&&new Set(m.map(_=>_.feeTier).filter(_=>typeof _=="number")).size>1){let _=this.dedupeByFeeTier(m).slice(0,5);return{_instructions:"The pair has multiple fee tiers. Present them as a short numbered list (fee tier %, 24h volume). Ask which fee tier the user wants, then re-call with the same token symbols + chosen feeTier. If the user already mentioned a deposit amount, pass it along as depositUsd. Do NOT mention tool names. Format numbers human-readably (e.g. $12.5M, 0.05%).",reason:`${r}/${o} exists across multiple fee tiers \u2014 user choice required.`,missing:["feeTier"],candidates:_.map(C=>({pair:C.token0?.symbol&&C.token1?.symbol?`${C.token0.symbol}/${C.token1.symbol}`:void 0,feeTierBps:C.feeTier,feeTierPercent:typeof C.feeTier=="number"?C.feeTier/1e4:void 0,volume24hUsd:C.volumeUsd24hr,address:C.id}))}}let h=m[0];if(!h.id||!ys.test(h.id))return{error:"Resolved pool has no valid address."};let f=await this.service.getV3PoolDetail({address:h.id,chain:n});if(!f.success||!f.data)return{error:f.error||"Failed to fetch pool detail."};let g=f.data,y=g.totalLiquidity?.value,k=g.volume24h?.value,w=g.feeTier,b=typeof w=="number"?w/1e4:void 0,T=typeof k=="number"&&typeof b=="number"?k*b/100:void 0,P=typeof T=="number"&&typeof y=="number"&&y>0?T/y*Ao:void 0,x={address:g.address,pair:g.token0?.symbol&&g.token1?.symbol?`${g.token0.symbol}/${g.token1.symbol}`:void 0,feeTierBps:w,feeTierPercent:b,tvlUsd:y,volume24hUsd:k,fees24hUsd:T,aprPercent:typeof P=="number"?P*100:void 0,chain:n,token0:g.token0?{symbol:g.token0.symbol}:void 0,token1:g.token1?{symbol:g.token1.symbol}:void 0},A=o?void 0:`User only provided ${r}; auto-picked the highest-volume V3 pool containing it.`;if(P==null)return{_instructions:"Pool APR could not be computed (TVL or volume data unavailable). Inform the user briefly and show TVL/volume if present. Do NOT mention tool names.",pool:x,autoPickedReason:A,aprUnavailableReason:y?"Volume data unavailable":"TVL is zero or unavailable"};let I=bs(i,P);return{_instructions:"Present the yield estimate naturally in the user's language. "+(a?"Lead with: pool (pair + fee %), chain, APR %. Then show projected earnings for the user's deposit \u2014 daily, weekly, monthly, yearly. ":'IMPORTANT: depositUsd was NOT specified by the user \u2014 the result is a per-$1000 EXAMPLE. Lead with: pool (pair + fee %), chain, APR %. Then say "for every $1000 deposited you would earn approximately:" and list daily / weekly / monthly / yearly. End with a short note that the user can multiply these numbers by (their deposit / 1000) for a custom amount. ')+(A?'Mention briefly that you picked the highest-volume pool for this token (do NOT say "auto-picked"). ':"")+"Add a one-line disclaimer about impermanent loss. Format USD numbers with 2 decimal places (e.g. $3.45/day). Do NOT mention tool names.",pool:x,estimate:I,depositMode:a?"user-specified":"default-1000-example",autoPickedReason:A,disclaimer:"Fee yield is based on the pool's 24h volume snapshot and assumes full-range liquidity. Actual returns vary with price movement, volume changes, and impermanent loss."}}filterPools(e,t,n,r){let o=t.toLowerCase(),s=n?n.toLowerCase():"";return e.filter(a=>{if(r!=null&&a.feeTier!==r)return!1;let i=(a.token0?.symbol??"").toLowerCase(),c=(a.token1?.symbol??"").toLowerCase();return s?this.sym(i,o)&&this.sym(c,s)||this.sym(i,s)&&this.sym(c,o):this.sym(i,o)||this.sym(c,o)})}sym(e,t){return!e||!t?!1:e.includes(t)||t.includes(e)}rankByVolume(e){return[...e].sort((t,n)=>(n.volumeUsd24hr??0)-(t.volumeUsd24hr??0))}dedupeByFeeTier(e){let t=new Set,n=[];for(let r of e)typeof r.feeTier=="number"&&(t.has(r.feeTier)||(t.add(r.feeTier),n.push(r)));return n}describeQuery(e,t,n){let r=t?`${e}/${t}`:e;return n!=null?`${r} at ${n} bps`:r}};var ws={"0x1":"https://gateway.thegraph.com/api/subgraphs/id/5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV","0xa4b1":"https://gateway.thegraph.com/api/subgraphs/id/FbCGRftH4a3yZugY7TnbYgPJVEv2LvMT6oF1fxPe9aJM","0x89":"https://gateway.thegraph.com/api/subgraphs/id/3hCPRGf4z88VC5rsBKU5AA9FBBq5nF3jbKJG7VZCbhjm","0x38":"https://gateway.thegraph.com/api/subgraphs/id/GcKPSgHoY42xNYVAkSPDhXSzi6aJDRQSKqBSXezL47gV","0x2105":"https://gateway.thegraph.com/api/subgraphs/id/HMuAwufqZ1YCRmzL2SfHTVkzZovC9VL2UAKhjvRqKiR1","0xa":"https://gateway.thegraph.com/api/subgraphs/id/Cghf4LfVqPiFw6fp6Y5X5Ubc8UpmUhSfJL82zwiBFLaj","0xa86a":"https://gateway.thegraph.com/api/subgraphs/id/GVH9h9KZ9CqheUEL93qMbq7QwgoBu32QXQDPR6bev4Eo"},ks={"0x1":"ethereum","0xa4b1":"arbitrum","0x89":"polygon","0x38":"bsc","0x2105":"base","0xa":"optimism","0xa86a":"avalanche"},Ts={"0x1":1,"0xa4b1":42161,"0x89":137,"0x38":56,"0x2105":8453,"0xa":10,"0xa86a":43114},_o={"0x1":"0x1",1:"0x1",eth:"0x1",ethereum:"0x1",mainnet:"0x1","0xa4b1":"0xa4b1",42161:"0xa4b1",arb:"0xa4b1",arbitrum:"0xa4b1","0x89":"0x89",137:"0x89",matic:"0x89",polygon:"0x89","0x38":"0x38",56:"0x38",bnb:"0x38",bsc:"0x38","0x2105":"0x2105",8453:"0x2105",base:"0x2105","0xa":"0xa",10:"0xa",op:"0xa",optimism:"0xa","0xa86a":"0xa86a",43114:"0xa86a",avax:"0xa86a",avalanche:"0xa86a"},Co={100:.01,500:.05,3e3:.3,1e4:1},vs={ethereum:"0x1","arbitrum one":"0xa4b1",arbitrum:"0xa4b1",polygon:"0x89",bsc:"0x38",base:"0x2105",optimism:"0xa","avalanche c-chain":"0xa86a",avalanche:"0xa86a"},Ss=300*1e3,X=class{apiKey;subgraphUrls;coinPoolBaseUrl;keyringPoolBaseUrl;llamaCache=null;llamaCacheAt=0;constructor(e={}){this.apiKey=e.theGraphApiKey||"4c67ac7a75b21befbd28dc9120c709f1",this.subgraphUrls={...ws,...e.subgraphUrls||{}},this.coinPoolBaseUrl=e.coinPoolBaseUrl||"https://api.coinpool.app",this.keyringPoolBaseUrl=e.keyringPoolBaseUrl||"https://pool-data.keyring.app"}resolveChain(e){if(!e)return"0x1";let t=e.toLowerCase();return _o[t]||(this.subgraphUrls[t]?t:"0x1")}isSupported(e){if(!e)return!1;let t=e.toLowerCase(),n=_o[t];return!!(n&&this.subgraphUrls[n])}chainName(e){let t=this.resolveChain(e);return ks[t]||t}numericChainId(e){return Ts[this.resolveChain(e)]}listSupportedChains(){return Object.keys(this.subgraphUrls)}async querySubgraph(e,t){let n=this.resolveChain(e),r=this.subgraphUrls[n];if(!r)throw new Error(`No subgraph available for chain: ${e}`);let o=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:JSON.stringify({query:t})});if(!o.ok)throw new Error(`Subgraph query failed: HTTP ${o.status}`);let s=await o.json();if(s.errors&&s.errors.length>0)throw new Error(`Subgraph error: ${s.errors[0].message||"Unknown"}`);return s.data}poolFragment(){return`
|
|
111
|
+
id
|
|
112
|
+
token0 { id symbol name }
|
|
113
|
+
token1 { id symbol name }
|
|
114
|
+
feeTier
|
|
115
|
+
liquidity
|
|
116
|
+
totalValueLockedUSD
|
|
117
|
+
volumeUSD
|
|
118
|
+
poolDayData(first: 1, orderBy: date, orderDirection: desc) {
|
|
119
|
+
volumeUSD
|
|
120
|
+
feesUSD
|
|
121
|
+
tvlUSD
|
|
122
|
+
date
|
|
123
|
+
liquidity
|
|
124
|
+
volumeToken0
|
|
125
|
+
volumeToken1
|
|
126
|
+
}
|
|
127
|
+
`}hasValidVolume(e){let t=e.poolDayData?.[0];return t?parseFloat(t.volumeToken0||"0")!==0&&parseFloat(t.volumeToken1||"0")!==0&&parseFloat(t.volumeUSD||"0")!==0:!1}sanitize(e){return!isFinite(e)||isNaN(e)||e>1e11?0:e}mapPool(e,t){let n=e,r=Number(n.feeTier||0),o=Co[r]??r/1e4,s=this.sanitize(parseFloat(n.totalValueLockedUSD||"0")),a=n.poolDayData?.[0],i=parseFloat(a?.volumeUSD||"0"),c=parseFloat(a?.feesUSD||"0"),u=null;return s>0&&c>0&&(u=parseFloat((c*365*100/s).toFixed(2))),{poolAddress:n.id,pair:`${n.token0.symbol}/${n.token1.symbol}`,token0:{symbol:n.token0.symbol,address:n.token0.id,name:n.token0.name},token1:{symbol:n.token1.symbol,address:n.token1.id,name:n.token1.name},tvl:s,volume24hUsd:i,fees24hUsd:c,apr:u,feeTierBps:r,feeTierPercent:o,liquidity:n.liquidity||"0",chain:this.chainName(t)}}async fetchDefiLlamaUniswap(){let e=Date.now();if(this.llamaCache&&e-this.llamaCacheAt<Ss)return this.llamaCache;try{let t=await fetch("https://yields.llama.fi/pools");if(!t.ok)throw new Error(`DefiLlama HTTP ${t.status}`);let r=((await t.json()).data||[]).filter(o=>o.project==="uniswap-v3");return this.llamaCache=r,this.llamaCacheAt=e,r}catch{return this.llamaCache??[]}}parseLlamaFeeTier(e){if(!e)return null;let t=e.match(/^([\d.]+)%/);return t?Math.round(parseFloat(t[1])*1e4):null}enrichWithLlama(e,t){return t.length?e.map(n=>{let r=this.resolveChain(n.chain),o=n.feeTierBps,s=n.token0.address.toLowerCase(),a=n.token1.address.toLowerCase(),i=t.filter(p=>vs[p.chain.toLowerCase()]===r),c=i.find(p=>{if(!p.underlyingTokens||p.underlyingTokens.length<2)return!1;let m=p.underlyingTokens.map(f=>f.toLowerCase());if(!(m.includes(s)&&m.includes(a)))return!1;let h=this.parseLlamaFeeTier(p.poolMeta);return h===null||h===o});if(!c){let p=n.token0.symbol.toUpperCase(),m=n.token1.symbol.toUpperCase(),h=[p,m].sort().join("-");c=i.find(f=>{if([...f.symbol.split("-").map(k=>k.toUpperCase())].sort().join("-")!==h)return!1;let y=this.parseLlamaFeeTier(f.poolMeta);return y===null||y===o})}if(!c)return n;let u=c.apyBase??c.apy??null,d={...n};return u!==null&&(d.apr=parseFloat(u.toFixed(2))),c.tvlUsd!=null&&(d.tvl=this.sanitize(c.tvlUsd)),c.volumeUsd1d!=null&&(d.volume24hUsd=this.sanitize(c.volumeUsd1d)),d}):e}async searchPools(e){let{tokens:t,chain:n,sortBy:r,filters:o}=e,s=this.resolveChain(n),a=t.map(f=>{let g=f.toUpperCase(),y=new Set([g]);return g.startsWith("W")||y.add("W"+g),g.startsWith("W")&&g.length>1&&y.add(g.slice(1)),[...y]}),i=['totalValueLockedUSD_gt: "10000"','liquidity_gt: "0"','volumeUSD_gt: "0"'];o?.feeTier&&i.push(`feeTier: "${o.feeTier}"`);let c="totalValueLockedUSD";r==="volume"?c="volumeUSD":r==="fee"?c="feeTier":r==="liquidity"&&(c="liquidity");let u=r==="fee"?"asc":"desc",d=f=>f.length===1?`symbol: "${f[0]}"`:`symbol_in: [${f.map(g=>`"${g}"`).join(", ")}]`,p=(f,g)=>`{
|
|
128
|
+
pools(
|
|
129
|
+
first: ${g}
|
|
130
|
+
orderBy: ${c}
|
|
131
|
+
orderDirection: ${u}
|
|
132
|
+
where: { ${[...i,...f].join(", ")} }
|
|
133
|
+
) {
|
|
134
|
+
${this.poolFragment()}
|
|
135
|
+
}
|
|
136
|
+
}`,m;if(a.length>0){let g,y;a.length>=2?(g=p([`token0_: { ${d(a[0])} }`,`token1_: { ${d(a[1])} }`],100),y=p([`token0_: { ${d(a[1])} }`,`token1_: { ${d(a[0])} }`],100)):(g=p([`token0_: { ${d(a[0])} }`],100),y=p([`token1_: { ${d(a[0])} }`],100));let[k,w]=await Promise.allSettled([this.querySubgraph(s,g),this.querySubgraph(s,y)]),b=k.status==="fulfilled"?(k.value?.pools||[]).filter(x=>this.hasValidVolume(x)).map(x=>this.mapPool(x,s)):[],T=w.status==="fulfilled"?(w.value?.pools||[]).filter(x=>this.hasValidVolume(x)).map(x=>this.mapPool(x,s)):[],P=new Set(b.map(x=>x.poolAddress));m=[...b,...T.filter(x=>!P.has(x.poolAddress))]}else m=((await this.querySubgraph(s,p([],100)))?.pools||[]).filter(g=>this.hasValidVolume(g)).map(g=>this.mapPool(g,s));let h=await this.fetchDefiLlamaUniswap();return h.length>0&&(m=this.enrichWithLlama(m,h)),o?.minTVL!=null&&(m=m.filter(f=>f.tvl>=o.minTVL)),o?.maxTVL!=null&&(m=m.filter(f=>f.tvl<=o.maxTVL)),o?.minAPR!=null&&(m=m.filter(f=>f.apr!=null&&f.apr>=o.minAPR)),o?.maxAPR!=null&&(m=m.filter(f=>f.apr!=null&&f.apr<=o.maxAPR)),r==="apr"?m.sort((f,g)=>(g.apr??0)-(f.apr??0)):r==="liquidity"&&m.sort((f,g)=>{let y=BigInt(g.liquidity||"0")-BigInt(f.liquidity||"0");return y>0n?1:y<0n?-1:0}),m}async getTrendingPools(e){let t=this.resolveChain(e.chain),n=`{
|
|
137
|
+
pools(
|
|
138
|
+
first: 100
|
|
139
|
+
orderBy: volumeUSD
|
|
140
|
+
orderDirection: desc
|
|
141
|
+
where: { totalValueLockedUSD_gt: "10000", liquidity_gt: "0", volumeUSD_gt: "0" }
|
|
142
|
+
) {
|
|
143
|
+
${this.poolFragment()}
|
|
144
|
+
}
|
|
145
|
+
}`,o=((await this.querySubgraph(t,n))?.pools||[]).filter(a=>this.hasValidVolume(a)).map(a=>this.mapPool(a,t)),s=await this.fetchDefiLlamaUniswap();return s.length>0&&(o=this.enrichWithLlama(o,s)),o}async getPoolByAddress(e){let t=this.resolveChain(e.chain),n=`{
|
|
146
|
+
pool(id: "${e.poolAddress.toLowerCase()}") {
|
|
147
|
+
${this.poolFragment()}
|
|
148
|
+
}
|
|
149
|
+
}`,r=await this.querySubgraph(t,n);if(!r?.pool)return null;let o=this.mapPool(r.pool,t),s=await this.fetchDefiLlamaUniswap();return s.length>0&&(o=this.enrichWithLlama([o],s)[0]),o}async getPoolByPositionId(e){let t=this.resolveChain(e.chain),n=`{
|
|
150
|
+
position(id: "${e.positionId}") {
|
|
151
|
+
pool {
|
|
152
|
+
${this.poolFragment()}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}`,r=await this.querySubgraph(t,n);if(!r?.position?.pool)return null;let o=this.mapPool(r.position.pool,t),s=await this.fetchDefiLlamaUniswap();return s.length>0&&(o=this.enrichWithLlama([o],s)[0]),o}async getPositionDetail(e){let t=this.resolveChain(e.chain),n=`{
|
|
156
|
+
position(id: "${e.positionId}") {
|
|
157
|
+
id
|
|
158
|
+
liquidity
|
|
159
|
+
depositedToken0
|
|
160
|
+
depositedToken1
|
|
161
|
+
withdrawnToken0
|
|
162
|
+
withdrawnToken1
|
|
163
|
+
collectedFeesToken0
|
|
164
|
+
collectedFeesToken1
|
|
165
|
+
pool {
|
|
166
|
+
${this.poolFragment()}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}`,r=await this.querySubgraph(t,n);if(!r?.position?.pool)return null;let o=r.position,s=this.mapPool(o.pool,t),a=await this.fetchDefiLlamaUniswap(),i=a.length>0?this.enrichWithLlama([s],a)[0]:s,c=i.feeTierBps,u=i.feeTierPercent;return{position:{positionId:o.id,poolAddress:i.poolAddress,pair:i.pair,token0:{symbol:i.token0.symbol,address:i.token0.address},token1:{symbol:i.token1.symbol,address:i.token1.address},feeTierBps:c,feeTierPercent:u,chain:i.chain,liquidity:o.liquidity,depositedToken0:o.depositedToken0,depositedToken1:o.depositedToken1,withdrawnToken0:o.withdrawnToken0,withdrawnToken1:o.withdrawnToken1,collectedFeesToken0:o.collectedFeesToken0,collectedFeesToken1:o.collectedFeesToken1},pool:i}}async getCoinPoolPairs(e){if(!this.coinPoolBaseUrl||!this.keyringPoolBaseUrl)return[];let t=this.numericChainId(e.chain);if(!t)return[];let n;try{let a=await fetch(`${this.coinPoolBaseUrl}/pair/pure-list?chainId=${t}`);if(!a.ok)throw new Error(`HTTP ${a.status}`);n=(await a.json())?.pairs||[]}catch{return[]}if(e.tokens&&e.tokens.length>=2){let a=e.tokens[0].toUpperCase(),i=e.tokens[1].toUpperCase();if(n=n.filter(c=>{let u=c.token0.symbol.toUpperCase(),d=c.token1.symbol.toUpperCase();return u.includes(a)&&d.includes(i)||u.includes(i)&&d.includes(a)}),n.length===0)return[]}let r=[];for(let a of n)for(let i of a.transaction||[])i.nftId&&r.push(i.nftId);let o=await this.fetchPositionDetails(t,r),s=new Map;for(let a of o){let i=String(a?.tokenId??"");i&&s.set(i,a)}return n.map(a=>this.mapCoinPoolPair(a,s)).filter(a=>a!==null)}mapCoinPoolPair(e,t){let n;for(let y of e.transaction||[])if(y.nftId&&(n=t.get(y.nftId),n))break;if(!n)return null;let r=e.selectorAddress.toLowerCase()===e.token0.address.toLowerCase(),o=n.tick?.minTick!=null?Number(n.tick.minTick):0,s=n.tick?.maxTick!=null?Number(n.tick.maxTick):0,a=n.tick?.currentTick!=null?Number(n.tick.currentTick):0,i=r?o:s>0?1/s:0,c=r?s:o>0?1/o:0,u=r?a:a>0?1/a:0,d=r?e.token0.symbol:e.token1.symbol,p=r?e.token1.symbol:e.token0.symbol;if(!u||!i&&!c)return null;let m=Math.min(i,c),h=Math.max(i,c);if(u<m||u>h)return null;let f=Number(e.fee),g=Co[f]??f/1e4;return{poolId:e.poolId,pair:`${e.token0.symbol}/${e.token1.symbol}`,fee:`${g}%`,apr:n.apr??null,aprAvg:n.aprAvg??null,token0Price:e.token0.price,token1Price:e.token1.price,priceRange:{minPrice:i,maxPrice:c,currentPrice:u,baseToken:d,quoteToken:p}}}async fetchPositionDetails(e,t){if(!this.keyringPoolBaseUrl||t.length===0)return[];let n=xs(t,200);return(await Promise.allSettled(n.map(async o=>{let s=new URLSearchParams({tokenIds:o.join(","),chainId:String(e)}),a=await fetch(`${this.keyringPoolBaseUrl}/user/positions-details/v3?${s}`);if(!a.ok)throw new Error(`HTTP ${a.status}`);let i=await a.json();return Array.isArray(i)?i:i?.data||[]}))).flatMap(o=>o.status==="fulfilled"?o.value:[])}};function xs(l,e){if(e<=0)return[l];let t=[];for(let n=0;n<l.length;n+=e)t.push(l.slice(n,n+e));return t}var Ps=["tvl","volume","apr","fee","liquidity"],yt=class extends v{name="subgraph-search-pools";description='Search Uniswap V3 liquidity pools via The Graph subgraphs by token symbol(s) on one or more chains. Use for: "find ETH/USDC pools on Base", "show OP pools", "stablecoin pools on Arbitrum", "best USDC pool". Returns pools with pair, TVL, 24h volume, fee tier (%), APR (DefiLlama-enriched), and liquidity rank. Supports TVL/APR/fee filters and sort by tvl, volume, apr, fee, or liquidity. Supported chains: Ethereum (0x1), Arbitrum (0xa4b1), Polygon (0x89), BSC (0x38), Base (0x2105), Optimism (0xa), Avalanche (0xa86a).';category="blockchain-data";noSuggestions=!0;parameters=[{name:"tokens",type:"array",items:{type:"string"},description:"Token symbols to match. 0 tokens = top pools by chosen sort. 1 token = pools containing it. 2 tokens = pools with that exact pair (both orderings tried). Wrapped variants (WETH\u2194ETH, WBTC\u2194BTC) auto-handled.",required:!1,default:[]},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]},{name:"sortBy",type:"string",description:'Sort key: "tvl" (default), "volume", "apr", "fee" (low-to-high), or "liquidity".',required:!1,default:"tvl"},{name:"minTVL",type:"number",description:"Minimum TVL in USD.",required:!1},{name:"maxTVL",type:"number",description:"Maximum TVL in USD.",required:!1},{name:"minAPR",type:"number",description:"Minimum APR percentage (e.g. 5 for 5%).",required:!1},{name:"maxAPR",type:"number",description:"Maximum APR percentage.",required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100, 500, 3000, or 10000.",required:!1},{name:"limit",type:"number",description:"Max pools to return after sort/filter. Default 5, max 20.",required:!1,default:5}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=(Array.isArray(e.tokens)?e.tokens:[]).filter(m=>typeof m=="string"&&m.trim().length>0).map(m=>m.trim().toUpperCase()),r=(Array.isArray(e.chains)?e.chains:[]).filter(m=>typeof m=="string"&&m.trim().length>0),o=r.filter(m=>!this.service.isSupported(m)),s=r.filter(m=>this.service.isSupported(m)).map(m=>this.service.resolveChain(m)),a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],i=typeof e.sortBy=="string"&&Ps.includes(e.sortBy)?e.sortBy:"tvl",c={minTVL:typeof e.minTVL=="number"?e.minTVL:void 0,maxTVL:typeof e.maxTVL=="number"?e.maxTVL:void 0,minAPR:typeof e.minAPR=="number"?e.minAPR:void 0,maxAPR:typeof e.maxAPR=="number"?e.maxAPR:void 0,feeTier:typeof e.feeTier=="number"?e.feeTier:void 0},u=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),20):5;if(r.length>0&&s.length===0)return{_instructions:"The user requested chains we do not support via The Graph subgraph. Politely list the unsupported chains and the supported chains. Do not show any pool data.",unsupportedChains:o,supportedChains:this.service.listSupportedChains(),pools:[],count:0};let p=(await Promise.allSettled(a.map(m=>this.service.searchPools({tokens:n,chain:m,sortBy:i,filters:c})))).flatMap(m=>m.status==="fulfilled"?m.value:[]);return i==="apr"?p.sort((m,h)=>(h.apr??0)-(m.apr??0)):i==="tvl"?p.sort((m,h)=>h.tvl-m.tvl):i==="volume"?p.sort((m,h)=>h.volume24hUsd-m.volume24hUsd):i==="fee"&&p.sort((m,h)=>m.feeTierBps-h.feeTierBps),p=p.slice(0,u),{_instructions:'Prefix the answer with: "According to the latest data from Subgraph Uniswap V3," (translate naturally into the user language; keep the word "Subgraph" as-is). Then list the pools with: pair (e.g. USDC/WETH), fee tier (%), TVL (USD), 24h volume (USD), APR (%). Format USD human-readably ($12.5M, $450K). Mention chain and sort metric briefly. If a pool has no APR (null), skip the APR field rather than showing zero. For EACH pool, render the provided uniswapUrl as a clickable markdown link \u2014 e.g. wrap the pair as "[USDC/WETH](uniswapUrl)" or append "[View on Uniswap](uniswapUrl)". Never omit this link. Do not invent URLs. If unsupportedChains is non-empty, mention them once at the end.',tokens:n,chains:a,unsupportedChains:o,sortBy:i,filters:c,count:p.length,pools:p.map(m=>this.formatPool(m))}}formatPool(e){return{pair:e.pair,poolAddress:e.poolAddress,chain:e.chain,feeTierBps:e.feeTierBps,feeTierPercent:e.feeTierPercent,tvlUsd:e.tvl,volume24hUsd:e.volume24hUsd,fees24hUsd:e.fees24hUsd,apr:e.apr,token0:e.token0,token1:e.token1,uniswapUrl:se(e)}}};function se(l){return`https://app.uniswap.org/explore/pools/${Gt[l.chain]||l.chain}/${l.poolAddress}`}var Gt={ethereum:"ethereum",arbitrum:"arbitrum",polygon:"polygon",bsc:"bnb",base:"base",optimism:"optimism",avalanche:"avalanche"};var bt=class extends v{name="subgraph-trending-pools";description='List Uniswap V3 pools with the highest 24h volume on one or more chains via The Graph subgraphs. Use for: "what are trending pools?", "most active pools", "hot pools right now", "pools with biggest volume today". Returns pair, TVL, 24h volume, fee tier (%), APR (DefiLlama-enriched) for each. Supported chains: Ethereum (0x1), Arbitrum (0xa4b1), Polygon (0x89), BSC (0x38), Base (0x2105), Optimism (0xa), Avalanche (0xa86a).';category="blockchain-data";noSuggestions=!0;parameters=[{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]},{name:"limit",type:"number",description:"Max pools to return. Default 5, max 20.",required:!1,default:5}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=(Array.isArray(e.chains)?e.chains:[]).filter(u=>typeof u=="string"&&u.trim().length>0),r=n.filter(u=>!this.service.isSupported(u)),o=n.filter(u=>this.service.isSupported(u)).map(u=>this.service.resolveChain(u));if(n.length>0&&o.length===0)return{_instructions:"User requested unsupported chains. Politely list them and the supported chains; do not show pool data.",unsupportedChains:r,supportedChains:this.service.listSupportedChains(),pools:[],count:0};let s=o.length>0?o:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),20):5,c=(await Promise.allSettled(s.map(u=>this.service.getTrendingPools({chain:u})))).flatMap(u=>u.status==="fulfilled"?u.value:[]);return c.sort((u,d)=>d.volume24hUsd-u.volume24hUsd),c=c.slice(0,a),{_instructions:'Prefix the answer with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). List pools ranked by 24h volume. For each: pair, fee %, 24h volume, TVL, APR (skip if null). For EACH pool render the provided uniswapUrl as a clickable markdown link (e.g. wrap the pair as "[USDC/WETH](uniswapUrl)" or append "[View on Uniswap](uniswapUrl)"). Never omit it. Do not invent URLs.',chains:s,unsupportedChains:r,count:c.length,pools:c.map(u=>({pair:u.pair,poolAddress:u.poolAddress,chain:u.chain,feeTierBps:u.feeTierBps,feeTierPercent:u.feeTierPercent,tvlUsd:u.tvl,volume24hUsd:u.volume24hUsd,fees24hUsd:u.fees24hUsd,apr:u.apr,token0:u.token0,token1:u.token1,uniswapUrl:se(u)}))}}};var wt=class extends v{name="subgraph-pool-by-address";description='Fetch full pool detail (pair, fee tier, TVL, 24h volume + fees, APR, token addresses) for a specific Uniswap V3 pool by its on-chain address via The Graph subgraphs. Use whenever the user pastes a 0x pool address: "tell me about pool 0xabc\u2026", "details of this pool 0x\u2026".';category="blockchain-data";noSuggestions=!0;parameters=[{name:"poolAddress",type:"string",description:"0x-prefixed Uniswap V3 pool contract address (40 hex chars).",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Defaults to the connected chain when omitted.',required:!1}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=typeof e.poolAddress=="string"?e.poolAddress.trim():"";if(!/^0x[a-fA-F0-9]{40}$/.test(n))return{error:"This only supports Uniswap V3 pools. Provide a valid Uniswap V3 pool address (a 0x-prefixed 40-character hex address)."};let r=typeof e.chain=="string"&&e.chain.trim()?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(r))return{_instructions:"The requested chain is not supported by the Subgraph pool service. List the supported chains briefly.",unsupportedChains:[r],supportedChains:this.service.listSupportedChains()};let o=this.service.resolveChain(r),s=await this.service.getPoolByAddress({poolAddress:n,chain:o});return s?{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show pair, fee tier (%), TVL, 24h volume, 24h fees, APR (skip if null), and the pool address. End the response with the provided uniswapUrl as a clickable markdown link, e.g. "[View on Uniswap](uniswapUrl)". Never omit it. Do not invent URLs.',chain:o,pool:{pair:s.pair,poolAddress:s.poolAddress,chain:s.chain,feeTierBps:s.feeTierBps,feeTierPercent:s.feeTierPercent,tvlUsd:s.tvl,volume24hUsd:s.volume24hUsd,fees24hUsd:s.fees24hUsd,apr:s.apr,token0:s.token0,token1:s.token1,uniswapUrl:se(s)}}:{error:`No pool found at ${n} on chain ${o}.`}}};var kt=class extends v{name="subgraph-pool-by-position-id";description='Return the pool underlying a Uniswap V3 NFT position id. Use when the user wants the pool stats for a numeric id WITHOUT the deposit/withdraw/fee history (e.g. "which pool is position 962961 in?", "show the pool of id 12345"). For full position history use subgraph-position-detail instead.';category="blockchain-data";noSuggestions=!0;parameters=[{name:"positionId",type:"string",description:"Numeric Uniswap V3 NFT position id as a string.",required:!0},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=typeof e.positionId=="string"?e.positionId.trim():"";if(!/^\d+$/.test(n))return{error:"positionId must be a numeric string."};let r=(Array.isArray(e.chains)?e.chains:[]).filter(u=>typeof u=="string"&&u.trim().length>0),o=r.filter(u=>!this.service.isSupported(u)),s=r.filter(u=>this.service.isSupported(u)).map(u=>this.service.resolveChain(u));if(r.length>0&&s.length===0)return{_instructions:"Requested chains are not supported. List the supported chains.",unsupportedChains:o,supportedChains:this.service.listSupportedChains()};let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],c=(await Promise.allSettled(a.map(u=>this.service.getPoolByPositionId({positionId:n,chain:u})))).map(u=>u.status==="fulfilled"?u.value:null).find(u=>u!=null);return c?{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show the pool of that position: pair, fee %, TVL, 24h volume, APR. Mention the position id. End the response with the provided uniswapUrl as a clickable markdown link, e.g. "[View on Uniswap](uniswapUrl)". Never omit it. Do not invent URLs.',positionId:n,pool:{pair:c.pair,poolAddress:c.poolAddress,chain:c.chain,feeTierBps:c.feeTierBps,feeTierPercent:c.feeTierPercent,tvlUsd:c.tvl,volume24hUsd:c.volume24hUsd,fees24hUsd:c.fees24hUsd,apr:c.apr,token0:c.token0,token1:c.token1,uniswapUrl:se(c)}}:{_instructions:"No pool found for this position id on the chosen chains. Ask the user to verify the id or chain.",positionId:n,chains:a,found:!1}}};var Tt=class extends v{name="subgraph-position-detail";description=`Look up a Uniswap V3 liquidity position by its numeric NFT id (e.g. 962961) on one or more chains via The Graph subgraphs. Returns the position (deposited/withdrawn token amounts, collected fees, liquidity) AND its underlying pool (pair, fee tier, TVL, 24h volume, APR). Use when the user names a numeric id with phrases like: "position 12345", "pool id 962961", "tell me about position 962961". Does NOT support the user's wallet-wide LP list \u2014 only a specific id.`;category="blockchain-data";noSuggestions=!0;parameters=[{name:"positionId",type:"string",description:'Numeric Uniswap V3 NFT position id as a string (e.g. "962961").',required:!0},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=typeof e.positionId=="string"?e.positionId.trim():"";if(!/^\d+$/.test(n))return{error:"positionId must be a numeric string (Uniswap V3 NFT id)."};let r=(Array.isArray(e.chains)?e.chains:[]).filter(d=>typeof d=="string"&&d.trim().length>0),o=r.filter(d=>!this.service.isSupported(d)),s=r.filter(d=>this.service.isSupported(d)).map(d=>this.service.resolveChain(d));if(r.length>0&&s.length===0)return{_instructions:"User asked about a position on chains we do not support. List unsupported + supported chains.",unsupportedChains:o,supportedChains:this.service.listSupportedChains()};let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],c=(await Promise.allSettled(a.map(d=>this.service.getPositionDetail({positionId:n,chain:d})))).map(d=>d.status==="fulfilled"?d.value:null).find(d=>d!=null);if(!c)return{_instructions:"No position with this id was found on the chosen chains. Suggest the user double-check the id or specify another chain.",positionId:n,chains:a,found:!1};let u=Gt[c.position.chain]||c.position.chain;return{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show the pair, chain, fee tier (%), deposited/withdrawn/collected fees for each token, plus pool stats (TVL, 24h volume, APR). End the response with TWO clickable markdown links from the data: position.uniswapPositionUrl as "[View position on Uniswap](uniswapPositionUrl)" and pool.uniswapUrl as "[View pool on Uniswap](uniswapUrl)". Never omit them. Do not invent URLs.',positionId:n,position:{...c.position,uniswapPositionUrl:`https://app.uniswap.org/positions/v3/${u}/${c.position.positionId}`},pool:{...c.pool,uniswapUrl:se(c.pool)}}}};var vt=class extends v{name="subgraph-coinpool-pairs";description='List CoinPool concentrated-liquidity price-range candidates for a token pair on a chain. Each entry shows: pair, fee tier, min/max price range, current price, and APR within that range (NOT the overall pool APR \u2014 strictly per-range). Use ONLY for queries about price ranges or which range to pick: "what price range should I use for ETH/USDC?", "best range for USDC/WETH on Arbitrum", "g\u1EE3i \xFD kho\u1EA3ng gi\xE1 ETH/USDC". Do NOT use for general "show me ETH/USDC pools" \u2014 use subgraph-search-pools for that.';category="blockchain-data";noSuggestions=!0;parameters=[{name:"tokens",type:"array",items:{type:"string"},description:'Two token symbols (e.g. ["ETH","USDC"]).',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Defaults to the connected chain when omitted.',required:!1},{name:"limit",type:"number",description:"Max entries to return (sorted by APR desc). Default 10.",required:!1,default:10}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=(Array.isArray(e.tokens)?e.tokens:[]).filter(c=>typeof c=="string"&&c.trim().length>0).map(c=>c.trim().toUpperCase());if(n.length<2)return{error:'tokens must contain two token symbols (e.g. ["ETH","USDC"]).'};let r=typeof e.chain=="string"&&e.chain.trim()?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(r))return{_instructions:"Requested chain not supported. Mention supported chains.",unsupportedChains:[r],supportedChains:this.service.listSupportedChains()};let o=this.service.resolveChain(r),s=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),30):10,a=await this.service.getCoinPoolPairs({chain:o,tokens:n});if(a.length===0)return{_instructions:"No CoinPool range data is available for this pair on this chain. Tell the user this nicely and suggest using general pool data instead (via subgraph-search-pools).",tokens:n,chain:o,pairs:[],count:0};let i=[...a].sort((c,u)=>(u.apr??-1/0)-(c.apr??-1/0)).slice(0,s);return{_instructions:'CRITICAL: APR in each entry is the APR for that SPECIFIC price range only \u2014 NEVER present it as the overall pool APR. Always show the min\u2013max price together with the APR, e.g. "APR X% (range: 1800 \u2013 2200 ETH/USDC)". Sort by APR descending; explain that the top entry is the best concentrated range but narrower = higher out-of-range risk.',tokens:n,chain:o,count:i.length,pairs:i}}};function ue(l){if(typeof l=="number")return Number.isFinite(l)&&l>0?{kind:"token",value:l,raw:String(l)}:null;if(typeof l!="string")return null;let e=l.trim();if(!e)return null;if(/^(?:max(?:imum)?|all|everything|tất\s*cả|toàn\s*bộ)$/i.test(e))return{kind:"percent",percent:100,raw:e};let t=e.match(/^%?\s*([0-9]*\.?[0-9]+)\s*%$|^%\s*([0-9]*\.?[0-9]+)$/);if(t){let o=parseFloat(t[1]??t[2]);return Number.isFinite(o)&&o>0?{kind:"percent",percent:o,raw:e}:null}let n=e.match(/^\$\s*([0-9]*\.?[0-9]+)$|^([0-9]*\.?[0-9]+)\s*\$$|^(?:usd)\s*([0-9]*\.?[0-9]+)$|^([0-9]*\.?[0-9]+)\s*usd$/i);if(n){let o=parseFloat(n[1]??n[2]??n[3]??n[4]);return Number.isFinite(o)&&o>0?{kind:"usd",usd:o,raw:e}:null}let r=parseFloat(e);return Number.isFinite(r)&&r>0&&/^[0-9]*\.?[0-9]+$/.test(e)?{kind:"token",value:r,raw:e}:null}function Uo(l,e){switch(l.kind){case"token":return{ok:!0,amount:l.value};case"percent":return e.balance===void 0||!Number.isFinite(e.balance)||e.balance<=0?{ok:!1,reason:"no_balance"}:{ok:!0,amount:e.balance*l.percent/100};case"usd":return e.usdPrice===void 0||!Number.isFinite(e.usdPrice)||e.usdPrice<=0?{ok:!1,reason:"no_price"}:{ok:!0,amount:l.usd/e.usdPrice}}}var As={send:30000n,wrap:40000n,swap:1000000n},_s={"0x1":["https://ethereum-rpc.publicnode.com","https://eth.drpc.org","https://1rpc.io/eth"],"0xa":["https://optimism-rpc.publicnode.com","https://optimism.drpc.org"],"0x38":["https://bsc-rpc.publicnode.com","https://bsc.drpc.org"],"0x89":["https://polygon-bor-rpc.publicnode.com","https://polygon.drpc.org"],"0x2105":["https://base-rpc.publicnode.com","https://base.drpc.org"],"0xa4b1":["https://arbitrum-one-rpc.publicnode.com","https://arbitrum.drpc.org"],"0xa86a":["https://avalanche-c-chain-rpc.publicnode.com","https://avalanche.drpc.org"],"0xe708":["https://linea-rpc.publicnode.com","https://linea.drpc.org"]};async function Cs(l){let e=l.toLowerCase(),n=[ut(l),..._s[e]??[]].filter(r=>!!r);for(let r of n)try{let o=await fe(r,"eth_gasPrice",[]),s=BigInt(o);if(s>0n)return s}catch{}return 0n}async function St(l,e){let t=await Cs(l);return t<=0n?0n:As[e]*t}function xt(l,e,t){if(e<=0n)return l;let n=z(t)?.native.decimals;if(n===void 0)return l;let r=Number(e)/10**n;return!Number.isFinite(r)||r<=0?l:Math.max(0,l-r)}var J=class extends v{kind="action";category="wallet-action";noSuggestions=!0;userInputFields=[];moralis;constructor(e){super(),e!==void 0&&(this.moralis=new E(e))}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=D(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let c=i.data.result.find(s);if(c)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let c=a.data.find(s)??a.data[0];if(c?.token_address)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.decimals:void 0}}}parseDecimals(e){if(typeof e=="number"&&Number.isInteger(e)&&e>=0)return e;if(typeof e=="string"&&e.trim()){let t=parseInt(e,10);if(Number.isInteger(t)&&t>=0)return t}}async resolveTokenRef(e){let{contractArg:t,symbolArg:n,decimalsArg:r,chain:o,walletAddress:s}=e,a=this.parseDecimals(r),i=typeof n=="string"&&n.trim()?n.trim():void 0,c=this.normaliseAddress(t),u=typeof t=="string"&&t.trim()?t.trim():void 0,d=c??i??u;if(!d)return{token:void 0};let p=await this.resolveContractAddress(d,o,s);return p?{token:{address:p.address,symbol:i??p.symbol,name:p.name,decimals:a??p.decimals}}:c?{token:{address:c,symbol:i,decimals:a}}:{error:"token_not_found",key:d}}requireWallet(e){let t=e?.walletAddress;return t?{ok:!0,address:t}:{ok:!1,payload:{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before submitting the action. Do NOT render the form."}}}requireChain(e,t){return Yn(e.chain,t)}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return D(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=parseFloat(t);return Number.isFinite(n)&&n>0?t:null}async resolveAmountInput(e){let{rawAmount:t,tokenAddress:n,chain:r,walletAddress:o,gasKind:s}=e,a=ue(t);if(!a)return null;let i=e.symbol??"the token",c=n.toLowerCase()==="native",u=!!s&&c&&!!r;if(a.kind==="token"&&!u)return{amount:this.toPlainDecimal(a.value)};let d=await this.readTokenBalanceAndPrice(n,r,o),p=d?.balance;if(u&&d?.balance!==void 0&&(p=await this.computeNativeSpendable(r,s,d.balance)),a.kind==="token")return p!==void 0&&a.value>p?{error:"amount_exceeds_spendable",_instructions:`The user wants to send ${this.toPlainDecimal(a.value)} ${i}, but after leaving enough to cover the network fee they can only send up to ${this.toPlainDecimal(p)} ${i}. Tell them, in their language, that ${this.toPlainDecimal(a.value)} ${i} is more than they can send and that the most they can send is ${this.toPlainDecimal(p)} ${i}, and ask them to pick a smaller amount. Do NOT mention gas, fees, tool names, UI, or forms \u2014 just refer to the spendable amount.`}:{amount:this.toPlainDecimal(a.value)};let m=Uo(a,{balance:p,usdPrice:d?.usdPrice});return m.ok?{amount:this.toPlainDecimal(m.amount)}:m.reason==="no_balance"?{error:"amount_balance_unavailable",_instructions:`Could not read the user's ${i} balance to work out ${a.raw} right now. Tell them, in their language, that the balance couldn't be fetched at the moment and ask them to enter an exact ${i} amount instead. Do NOT invent a balance or amount. Do NOT mention tool names, UI, or forms.`}:{error:"amount_price_unavailable",_instructions:`Could not get a USD price for ${i} to work out ${a.raw} right now. Tell them, in their language, that the price couldn't be fetched at the moment and ask them to enter an exact ${i} amount instead. Do NOT invent a price or amount. Do NOT mention tool names, UI, or forms.`}}async computeNativeSpendable(e,t,n){let r=await St(e,t);return xt(n,r,e)}async resolveNativeSpendableFormatted(e,t,n){if(!(!e||!t))try{let r=await this.readTokenBalanceAndPrice("native",e,t);if(r?.balance===void 0)return;let o=await this.computeNativeSpendable(e,n,r.balance);return this.toPlainDecimal(o)}catch{return}}async readTokenBalanceAndPrice(e,t,n){if(!this.moralis||!n)return;let r=await this.moralis.getWalletTokenBalances({address:n,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[],s=e.toLowerCase()==="native",a=e.toLowerCase(),i=o.find(u=>s?u.native_token===!0:(u.token_address??"").toLowerCase()===a);if(!i)return;let c=i.balance_formatted!=null?Number(i.balance_formatted):void 0;return{balance:Number.isFinite(c)?c:void 0,usdPrice:typeof i.usd_price=="number"&&i.usd_price>0?i.usd_price:void 0,balanceFormatted:i.balance_formatted,symbol:i.symbol}}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),c=a+i,u;if(s>=0){let d=a.length+s;u=d>=c.length?c.padEnd(d,"0"):`${c.slice(0,d)}.${c.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${c}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),o&&Array.isArray(o.actionButtons)&&o.actionButtons.length>0&&(s.actionButtons=o.actionButtons),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=this.requireWallet(t);if(!n.ok)return n.payload;let r=Ie(e.chain,t);if(r){let g=this.actionType.replace(/_/g," ");return{error:"wrong_chain",_instructions:`The user asked to ${g} on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to ${g} on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can ${g} on ${r.connectedLabel} (their current network) instead. Do NOT render the form. Do NOT mention tool names, UI, or forms.`}}let o=this.requireChain(e,t);if(!o)return{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'};let s=await this.buildParameters(e,t);if(s&&typeof s=="object"&&"error"in s&&typeof s.error=="string"||s&&typeof s=="object"&&"actionButtons"in s&&Array.isArray(s.actionButtons)||s&&typeof s=="object"&&"ui"in s&&s.ui&&typeof s.ui=="object"&&typeof s.ui.component=="string")return s;let a={action:this.actionType,chainId:o,walletAddress:n.address,parameters:s},i={component:this.component,props:a},c=a.parameters,u=g=>{let y=c[g];return y!=null&&y!==""},d=this.userInputFields.filter(g=>!(Array.isArray(g.key)?g.key:[g.key]).some(u)).map(g=>g.label),p=this.userInputFields.map(g=>{let k=(Array.isArray(g.key)?g.key:[g.key]).find(u);return k?`${g.label}: ${c[k]}`:null}).filter(g=>g!=null),m=this.actionType.replace(/_/g," "),h='NEVER invent, guess, or insert a placeholder (e.g. "[recipient address]") for any detail the user has not actually provided \u2014 only state values listed as already provided. Do NOT mention tool names, UI, or forms.',f=d.length>0?`The ${m} is in progress but still needs input from the user. Ask the user, in their language, to provide: ${d.join(", ")}. `+(p.length>0?`Already provided: ${p.join("; ")}. `:"")+`Do NOT say "review and confirm" yet \u2014 there are still missing details to collect. ${h} Wait for the user to provide the missing details before proceeding.`:`The ${m} has all required details. In the user's language, ask the user to review and confirm to complete the ${m}. `+(p.length>0?`Details provided: ${p.join("; ")}. `:"")+`${h} Wait for the user to confirm or edit before proceeding.`;return{ui:i,action:this.actionType,chainId:o,parameters:a.parameters,_instructions:f}}};var Pt=class extends J{name="open-send-native-form";actionType="send_native";component="SendNativeForm";userInputFields=[{key:"to_address",label:"the recipient address"},{key:"amount",label:"the amount to send"}];constructor(e){super(e)}async run(e,t){if(e.chain==null||e.chain===""){let n=zn(e.native_symbol);if(n)return super.run({...e,chain:n},t)}return super.run(e,t)}description=`Open the SEND NATIVE form for the chain's native coin (ETH/BNB/MATIC/AVAX/\u2026). Use for ANY native-send intent: "send eth", "send 0.1 ETH", "transfer 1 BNB to vitalik.eth", "g\u1EEDi ETH". Call EVEN IF the user did not name an amount or recipient \u2014 the form prompts for them. Do NOT use for ERC-20 tokens \u2014 use open-send-token-form instead.`;parameters=[{name:"chain",type:"string",description:'Hex chain id: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. Defaults to connected chain.',required:!1},{name:"native_symbol",type:"string",description:'The native coin the user named to send, VERBATIM (e.g. "ETH", "BNB", "MATIC", "AVAX"). ALWAYS set this when the user names the coin ("send bnb" \u2192 native_symbol="BNB"); leave blank only when they say just "send" with no coin. A coin that belongs to one specific chain (BNB\u2192BSC, MATIC/POL\u2192Polygon, AVAX\u2192Avalanche) selects that chain, so the tool can detect when the wallet is connected elsewhere.',required:!1},{name:"to_address",type:"string",description:"Recipient EVM address (0x\u2026). Omit when unknown \u2014 the form will prompt the user.",required:!1},{name:"amount",type:"string",description:'Amount of native coin to send. Accepted forms: a plain amount ("0.1"); a PERCENT of their balance ("50%"); the literal word "max" for the WHOLE balance; or a USD value ("5$", "$5", "5 usd"). NORMALIZE natural-language phrasing in ANY language to one of these before passing it: any "send everything / the whole balance / maximum" phrasing \u2014 regardless of language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") \u2014 MUST become the literal "max"; any "half" phrasing ("half", "m\u1ED9t n\u1EEDa", "\u4E00\u534A", "\u534A\u5206") MUST become "50%". Pass plain/percent/USD numbers VERBATIM. The tool converts percent/usd/max into a concrete amount itself (and for the native coin always leaves enough to cover the network fee). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={};if(e.to_address!=null){let a=this.normaliseAddress(e.to_address);a?n.to_address=a:typeof e.to_address=="string"&&e.to_address.trim()&&(n.to_address=e.to_address.trim())}let r=this.requireChain(e,t)??void 0,o=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:"native",symbol:"the native coin",chain:r,walletAddress:t?.walletAddress??void 0,gasKind:"send"});if(o&&"error"in o)return o;o&&(n.amount=o.amount);let s=await this.resolveNativeSpendableFormatted(r,t?.walletAddress??void 0,"send");return s!==void 0&&(n.spendable=s),n}};var At=class extends J{name="open-send-token-form";actionType="send_token";component="SendTokenForm";userInputFields=[{key:"to_address",label:"the recipient address"},{key:"amount",label:"the amount to send"}];constructor(e){super(e)}description=`Open the SEND TOKEN (ERC-20) form. Use for ANY ERC-20 send/transfer intent: "send token", "send USDC", "transfer 50 DAI to 0x\u2026", "g\u1EEDi 10 USDT". Call this tool EVEN IF the user did not name a token, amount, or recipient \u2014 the tool handles missing token via a picker, the form prompts for the rest. Do NOT use for the chain's native coin (ETH/BNB/MATIC/AVAX) \u2014 use open-send-native-form instead.`;parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:"ERC-20 token contract address (0x\u2026). Pass when known. Otherwise leave empty and provide token_symbol \u2014 the tool will look up the address.",required:!1},{name:"token_symbol",type:"string",description:'Token symbol (e.g. "USDC"). ALWAYS set this whenever the user names a token to send ("send usdc" \u2192 token_symbol="USDC"); only leave it blank when the user names NO token at all ("send token"), so the tool can show a wallet picker. Used to auto-resolve the contract address and to display the symbol in the form. (Pass either this or contract_address.)',required:!1},{name:"decimals",type:"number",description:"Token decimals (e.g. 6 for USDC, 18 for most ERC-20s). Optional.",required:!1},{name:"to_address",type:"string",description:"Recipient EVM address (0x\u2026). Omit when unknown.",required:!1},{name:"amount",type:"string",description:'Amount of the token to send. Accepted forms: a plain amount ("100"); a PERCENT of their balance ("50%"); the literal word "max" for the WHOLE balance; or a USD value ("5$", "$5", "5 usd"). NORMALIZE natural-language phrasing in ANY language to one of these before passing it: any "send everything / the whole balance / maximum" phrasing \u2014 regardless of language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") \u2014 MUST become "max"; any "half" phrasing ("half", "m\u1ED9t n\u1EEDa", "\u4E00\u534A", "\u534A\u5206") MUST become "50%". Pass plain/percent/USD numbers VERBATIM. The tool converts percent/usd/max into a concrete amount itself. Omit when unknown.',required:!1},{name:"send_prompt_template",type:"string",description:`A short imperative "send" command IN THE USER'S LANGUAGE, used as the label/command when the user has not yet picked which token to send (token-picker buttons). Write it in the SAME language the user is using right now. Use these exact placeholders (do not translate the braces): "{symbol}" for the token, "{amount}" for the amount, "{to}" for the recipient address. Put the localized verb and the "to" preposition directly in the text. English example: "send {amount} {symbol} to {to}". Vietnamese example: "g\u1EEDi {amount} {symbol} \u0111\u1EBFn {to}". Japanese example: "{to} \u306B {amount} {symbol} \u3092\u9001\u308B". Always include {symbol}. Include {amount}/{to} too \u2014 the tool removes any whose value is unknown.`,required:!0}];async buildParameters(e,t){let n=this.normaliseAddress(e.contract_address)!=null,r=typeof e.token_symbol=="string"&&e.token_symbol.trim().length>0;if(!n&&!r&&this.moralis){let u=this.requireChain(e,t)??void 0,d=t?.walletAddress;if(d){let p=await this.moralis.getWalletTokenBalances({address:d,chain:u,excludeSpam:!0,excludeUnverifiedContracts:!0}),h=(p.success?p.data?.result??[]:[]).filter(w=>w.symbol&&(w.native_token||w.token_address)).sort((w,b)=>(b.usd_value??0)-(w.usd_value??0)).slice(0,8);if(h.length===0)return{error:"no_token_holdings",_instructions:"The wallet does not hold any sendable tokens on this chain. Tell the user they have nothing to send and suggest they top up first. Do NOT open the form."};let f=e.to_address!=null?this.normaliseAddress(e.to_address):null,g=this.normaliseAmount(e.amount),y=typeof e.send_prompt_template=="string"?e.send_prompt_template:"";return{actionButtons:h.map(w=>{let b=w.symbol,T=w.balance_formatted?this.cleanAmountString(w.balance_formatted):"",P=w.name?w.name:"",x=w.usd_value?`($${w.usd_value.toFixed(2)})`:"";return{label:`${P}:${T} ${b} ${x}`.trim(),prompt:this.buildSendPrompt(y,b,g,f)}}),_instructions:"Reply briefly in the user's language: ask them to pick which token they want to send from the options below. Do NOT list the tokens in text \u2014 the options already show them. Do NOT mention tool names, UI, or forms."}}}let o=this.requireChain(e,t)??void 0,s=t?.walletAddress??void 0,a=await this.findOwnedTokenForSend(e,o,s);if(a&&"error"in a)return a;let i=a?.token,c={};if(i?.address&&(c.contract_address=i.address),i?.symbol&&(c.token_symbol=i.symbol),i?.decimals!==void 0&&(c.decimals=String(i.decimals)),e.to_address!=null){let u=this.normaliseAddress(e.to_address);u?c.to_address=u:typeof e.to_address=="string"&&e.to_address.trim()&&(c.to_address=e.to_address.trim())}if(i?.address){let u=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:i.address,symbol:i.symbol,chain:o,walletAddress:s});if(u&&"error"in u)return u;u&&(c.amount=u.amount)}else{let u=this.normaliseAmount(e.amount);u&&(c.amount=u)}return c}async findOwnedTokenForSend(e,t,n){let r=this.normaliseAddress(e.contract_address),o=typeof e.token_symbol=="string"&&e.token_symbol.trim()?e.token_symbol.trim():void 0,s=r??o;if(!this.moralis||!n||!s){let m=await this.resolveTokenRef({contractArg:e.contract_address,symbolArg:e.token_symbol,decimalsArg:e.decimals,chain:t,walletAddress:n});return"error"in m?{error:"token_not_found",symbol:m.key,_instructions:`Could not resolve token "${m.key}" to a contract address on this chain. Ask the user to provide the token contract directly.`}:{token:m.token}}let a=s.toLowerCase(),i=r!=null,c=await this.moralis.getWalletTokenBalances({address:n,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0}),d=(c.success?c.data?.result??[]:[]).find(m=>i?(m.token_address??"").toLowerCase()===a:(m.symbol??"").toLowerCase()===a);if(d&&d.balance&&!/^0*$/.test(d.balance.trim())){let m=this.parseDecimals(e.decimals);return{token:{address:d.token_address,symbol:d.symbol??o,name:d.name,decimals:m??(typeof d.decimals=="number"?d.decimals:void 0)}}}let p=d?.symbol??o??s;return{error:"no_balance",symbol:p,_instructions:`The user does not hold any ${p} in their wallet on this chain, so there is nothing to send. Tell them (in their language) that they don't have any ${p} to send, and suggest they top up or pick another token. Do NOT open a form. Do NOT mention tool names, UI, or forms.`}}buildSendPrompt(e,t,n,r){let s=(e&&e.includes("{symbol}")?e:"send {amount} {symbol} to {to}").replace(/\{symbol\}/g,t).replace(/\{amount\}/g,n??"");return r?s=s.replace(/\{to\}/g,r):s=s.replace(/\s*\S+\s*\{to\}/g,"").replace(/\{to\}\s*\S+\s*/g,"").replace(/\{to\}/g,""),s.replace(/\s+/g," ").trim()}};var _t=class extends J{name="open-approve-token-form";actionType="approve_token";component="ApproveTokenForm";userInputFields=[{key:"spender_address",label:"the spender address to approve"}];constructor(e){super(e)}description='Open the APPROVE TOKEN form so the user can grant a spender allowance on an ERC-20 token. Use when the user says "approve USDC for Uniswap", "c\u1EA5p quy\u1EC1n 100 USDT cho 0x\u2026", "revoke approval". Pass `contract_address` when you have it; otherwise pass `token_symbol` (e.g. "USDC") and the tool auto-resolves the contract. Always pass `spender_address` (the contract being approved). Pass `amount` for a specific allowance; omit for unlimited (max uint256). For approving 0 to revoke, pass amount="0".';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:"ERC-20 token contract address (0x\u2026). Pass when known. Otherwise leave empty and provide token_symbol.",required:!1},{name:"token_symbol",type:"string",description:'Token symbol (e.g. "USDC"). Required when contract_address is not given. Used to auto-resolve the contract.',required:!1},{name:"decimals",type:"number",description:"Token decimals.",required:!1},{name:"spender_address",type:"string",description:"EVM address of the spender contract being approved (0x\u2026). Omit when unknown \u2014 the form will prompt the user.",required:!1},{name:"amount",type:"string",description:'Human-readable allowance amount. Omit for UNLIMITED (max uint256). Pass "0" to revoke.',required:!1}];async buildParameters(e,t){let n=this.requireChain(e,t)??void 0,r=await this.resolveTokenRef({contractArg:e.contract_address,symbolArg:e.token_symbol,decimalsArg:e.decimals,chain:n,walletAddress:t?.walletAddress??void 0});if("error"in r)return{error:"token_not_found",symbol:r.key,_instructions:`Could not resolve token "${r.key}" to a contract address on this chain. Ask the user to provide the token contract directly.`};let o=r.token,s=this.normaliseAddress(e.spender_address),a={};if(o?.address&&(a.contract_address=o.address),s&&(a.spender_address=s),o?.symbol&&(a.token_symbol=o.symbol),o?.decimals!==void 0&&(a.decimals=String(o.decimals)),typeof e.amount=="string"&&e.amount.trim()){let i=e.amount.trim(),c=parseFloat(i);Number.isFinite(c)&&c>=0&&(a.amount=i)}else typeof e.amount=="number"&&Number.isFinite(e.amount)&&e.amount>=0&&(a.amount=String(e.amount));return a}};var Ct=class extends J{name="open-wrap-native-form";actionType="wrap_native";component="WrapNativeForm";userInputFields=[{key:"amount",label:"the amount to wrap"}];description='Open the WRAP NATIVE form so the user can wrap the chain\'s native coin into its wrapped ERC-20 (ETH \u2192 WETH, BNB \u2192 WBNB, MATIC \u2192 WMATIC, AVAX \u2192 WAVAX). Triggered by "wrap", "deposit into WETH", "convert ETH to WETH", "b\u1ECDc ETH". Only `amount` is needed. DO NOT use for arbitrary token swaps.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"amount",type:"string",description:'Human-readable amount of native coin to wrap (e.g. "0.5"). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={},r=this.requireChain(e,t)??void 0,o=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:"native",symbol:"the native coin",chain:r,walletAddress:t?.walletAddress??void 0,gasKind:"wrap"});if(o&&"error"in o)return o;o&&(n.amount=o.amount);let s=await this.resolveNativeSpendableFormatted(r,t?.walletAddress??void 0,"wrap");return s!==void 0&&(n.spendable=s),n}};var Ut=class extends J{name="open-unwrap-native-form";actionType="unwrap_native";component="UnwrapNativeForm";userInputFields=[{key:"amount",label:"the amount to unwrap"}];description='Open the UNWRAP NATIVE form so the user can unwrap their wrapped native into the chain\'s native coin (WETH \u2192 ETH, WBNB \u2192 BNB, WMATIC \u2192 MATIC, WAVAX \u2192 AVAX). Triggered by "unwrap", "withdraw from WETH", "convert WETH to ETH", "th\xE1o WETH". Only `amount` is needed.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"amount",type:"string",description:'Human-readable amount of WRAPPED native to unwrap (e.g. "0.5"). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={},r=this.normaliseAmount(e.amount);return r&&(n.amount=r),n}};var Rt=class extends v{name="open-buy-token-form";kind="action";category="wallet-action";noSuggestions=!0;moralis;pantograph;debug;constructor(e,t){super(),e!==void 0&&(this.moralis=new E(e),this.pantograph=new Y({baseUrl:e.pantographUrl})),this.debug=t?.debug??!1}dbg(e,t){this.debug&&(t===void 0?console.log(`[BuyTokenTool] ${e}`):console.log(`[BuyTokenTool] ${e}`,JSON.stringify(t)))}description='Open the BUY TOKEN flow for ANY buy/purchase intent. Handles both cases: (a) the user NAMED a token \u2014 "buy PEPE", "mua DOGE", "buy 100 USDC with ETH": pass `token_symbol` (the destination); if you also know the pay token, fill `pay_with_symbol` (or `pay_with_address`), else leave pay_with_* blank and the tool returns a wallet picker. (b) the user did NOT name a token \u2014 "buy a trending token", "mua token trending", "buy something pumping", "buy hot tokens", "mua token t\u0103ng m\u1EA1nh": leave `token_symbol` blank and the tool returns buttons of the chain\'s current trending tokens so the user picks one. Amount: `buy_amount` = destination to RECEIVE; `pay_with_amount` = payment to SPEND. Never both.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"token_symbol",type:"string",description:'Symbol of the token the user wants to BUY (e.g. "PEPE"). Leave EMPTY when the user did not name a token (e.g. "buy a trending token") \u2014 the tool then returns trending tokens to pick from.',required:!1},{name:"contract_address",type:"string",description:`Destination token address (0x\u2026), or "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from token_symbol.`,required:!1},{name:"buy_amount",type:"string",description:`Quantity of the DESTINATION token (the one being bought) the user wants to RECEIVE. Set this when the amount sits next to the token being bought: "buy 5 PEPE", "mua 5 PEPE", "buy 0.02 PCM with USDC" \u2192 buy_amount = the number next to PEPE/PCM. May also be a USD value when the user sizes the buy in dollars ("buy $5 of PEPE" \u2192 "5$"); pass the user's expression VERBATIM (the tool converts "$" to a token quantity). Mutually exclusive with pay_with_amount \u2014 set at most ONE of the two.`,required:!1},{name:"pay_with_symbol",type:"string",description:'Symbol of the payment token (e.g. "USDC", "ETH").',required:!1},{name:"pay_with_address",type:"string",description:`Address of the payment token (0x\u2026), OR "native" for the chain's native coin.`,required:!1},{name:"pay_with_amount",type:"string",description:'Quantity of the PAYMENT token (the one being spent) the user wants to SPEND. Set this when the amount sits next to the payment token: "buy PEPE with 10 USDC", "mua PEPE b\u1EB1ng 10 USDC", "spend 0.5 ETH on DOGE" \u2192 pay_with_amount = the number next to USDC/ETH. May also be a PERCENT of the pay-token balance ("buy USDC with 50% USDT" \u2192 "50%"), the literal word "max" meaning the WHOLE balance, or a USD value ("buy USDC with 5$ USDT" \u2192 "5$"). NORMALIZE any "whole balance / everything / maximum" phrasing in ANY language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") to the literal "max"; pass plain/percent/USD numbers VERBATIM (the tool converts "%"/"$"/"max"). "max" is an AMOUNT, never a token \u2014 still set pay_with_symbol to the payment token ("ETH" in "buy USDC with max ETH"). Mutually exclusive with buy_amount \u2014 set at most ONE of the two.',required:!1},{name:"limit",type:"number",description:"Only used when no token is named: max number of trending tokens to show as buttons. Default 6. Clamped to [1, 10].",required:!1},{name:"buy_prompt_template",type:"string",description:`A short "buy" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the trending-token picker (when the user named no token). Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "buy {token}". Vietnamese example: "mua {token}". Japanese example: "{token} \u3092\u8CB7\u3046". Always include {token}.`,required:!0},{name:"buy_with_prompt_template",type:"string",description:`A short "buy X with Y" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the pay-token picker and the percentage-spend buttons. Use these exact placeholders (do not translate the braces): "{token}" = token being bought, "{pay}" = payment token, "{buy_amount}" = quantity of the BOUGHT token (place it next to {token}), "{pay_amount}" = quantity of the PAYMENT token (place it next to {pay}). Put the localized verb and the "with" preposition directly in the text. English example: "buy {buy_amount} {token} with {pay_amount} {pay}". Vietnamese example: "mua {buy_amount} {token} b\u1EB1ng {pay_amount} {pay}". Japanese example: "{pay_amount} {pay} \u3067 {buy_amount} {token} \u3092\u8CB7\u3046". Always include {token} and {pay}. Include both {buy_amount} and {pay_amount} too \u2014 the tool removes whichever amount is unknown (only one side ever carries an amount).`,required:!0}];async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),o&&Array.isArray(o.actionButtons)&&o.actionButtons.length>0&&(s.actionButtons=o.actionButtons),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){if(!t?.walletAddress)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before buying. Do NOT proceed."};let r=Ie(e.chain,t);return r?{error:"wrong_chain",_instructions:`The user asked to buy on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to buy on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can buy on ${r.connectedLabel} (their current network) instead. Do NOT proceed. Do NOT mention tool names, UI, or forms.`}:this.requireChain(e,t)?this.buildResult(e,t):{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'}}async buildResult(e,t){let n=typeof e.token_symbol=="string"?e.token_symbol.trim():"",r=typeof e.contract_address=="string"?e.contract_address.trim():"",o=this.requireChain(e,t)??void 0,s=t?.walletAddress??void 0;if(this.dbg("buildResult:args",{args:e,chain:o,walletAddress:s,hasMoralis:!!this.moralis}),!n&&!r)return this.dbg("\u2192 Branch 0: trending picker (no token named)"),await this.buildTrendingPicker(e,t);let a=await this.resolveSide(e.contract_address,n,o,s,"dest"),i=a.address,c=a.symbol??n,u=a.decimals,d=await this.resolveSide(e.pay_with_address,e.pay_with_symbol,o,s,"pay"),p=d.address,m=d.symbol,h=d.decimals,f=await this.resolveBuyAmountSpec(e.buy_amount,i,o,c),g=p?await this.resolvePayAmountSpec(e.pay_with_amount,p,o,s,m):this.normaliseAmount(e.pay_with_amount),y=ue(e.pay_with_amount),k=!!y&&y.kind!=="token",w=null;if(this.dbg("resolved sides",{dest:{destContract:i,destSymbol:c,destDecimals:u},pay:{payAddr:p,paySymbol:m,payDecimals:h},buyAmount:f,payAmount:g}),!i)return this.dbg("\u2192 dest token not resolved"),{error:"dest_token_not_found",_instructions:`Could not find the token "${c||r}" to buy on this chain. Tell the user, in their language, that the token could not be found and ask them to check the name or provide its contract address. Do NOT invent an address. Do NOT mention tool names, UI, or forms.`};if(!p&&this.moralis&&t?.walletAddress)return this.dbg("\u2192 Branch 1: pay picker (pay token missing)"),await this.buildPayWithPicker({args:e,userContext:t,destSymbol:c,destContract:i,buyAmount:f,payAmount:g});if(!p)return{error:"pay_token_missing",_instructions:`Ask the user, in their language, which token they want to spend to buy ${c}. Do NOT invent a token. Do NOT mention tool names, UI, or forms.`};if(f&&!g){this.dbg("Branch 1.3: deriving pay amount from buy amount",{buyAmount:f,destSymbol:c,paySymbol:m});let b=await this.derivePayAmountFromBuy({chain:o,buyAmount:f,destContract:i,destSymbol:c,payAddr:p,paySymbol:m});if(!b)return this.dbg("\u2192 Branch 1.3: price unavailable \u2014 needs pay amount"),{error:"needs_pay_amount",_instructions:`Could not work out how much ${m??"of the payment token"} equals ${f} ${c} right now. Tell the user, in their language, that the price couldn't be fetched at the moment, and ask how much ${m??"of the payment token"} they want to spend instead. Do NOT invent an amount or a price. Do NOT mention tool names, UI, or forms.`};g=b,w=f,k=!1,this.dbg("\u2192 Branch 1.3: derived pay amount",{payAmount:g,derivedFromBuy:w})}if(this.moralis&&t?.walletAddress){this.dbg("Branch 1.4: checking pay balance",{payAddr:p,paySymbol:m,payAmount:g});let b=await this.checkPayBalance({args:e,userContext:t,chain:o,destSymbol:c,destContract:i,payAddr:p,paySymbol:m,payAmount:g,buyAmount:f,derivedFromBuy:w,amountSizedToBalance:k});if(b)return this.dbg("\u2192 Branch 1.4: balance shortfall \u2014 returning",{error:b.error,hasButtons:Array.isArray(b.actionButtons)}),b;this.dbg("Branch 1.4: balance OK \u2014 continuing")}if(!f&&!g&&this.moralis&&t?.walletAddress){this.dbg("Branch 1.5: building amount picker");let b=await this.buildAmountPicker({userContext:t,chain:o,destSymbol:c,payAddr:p,paySymbol:m,buyWithTemplate:typeof e.buy_with_prompt_template=="string"?e.buy_with_prompt_template:""});if(b)return this.dbg("\u2192 Branch 1.5: amount picker returned"),b;this.dbg("Branch 1.5: amount picker null \u2014 needs amount")}if(g&&(h!==void 0||p==="native")){this.dbg("Branch 1.75: building confirm tx");let b=await this.buildConfirmTx({userContext:t,chain:o,destContract:i,destSymbol:c,destDecimals:u,payAddr:p,paySymbol:m,payDecimals:h,payAmount:g});return b&&"ui"in b?(this.dbg("\u2192 Branch 1.75: confirm tx returned"),b):b&&"quoteError"in b?(this.dbg("\u2192 Branch 1.75: quote error from provider",{quoteError:b.quoteError}),{error:"quote_failed",quoteError:b.quoteError,_instructions:`Could not quote buying ${c} with ${m??"the selected token"}. The swap provider reported: "${b.quoteError}". Tell the user, in their language, exactly that reason (translate the wording, keep any numbers/limits verbatim) and suggest they adjust the amount or try again shortly. Do NOT invent numbers or a different reason. Do NOT mention tool names, UI, or forms.`}):(this.dbg("Branch 1.75: confirm tx unavailable (null)"),{error:"quote_failed",_instructions:`Could not get a quote to buy ${c} with ${m??"the selected token"} right now. Tell the user, in their language, that the swap could not be quoted at the moment and ask them to try again shortly or with a different amount. Do NOT invent numbers. Do NOT mention tool names, UI, or forms.`})}return this.dbg("\u2192 needs pay amount"),{error:"needs_pay_amount",_instructions:`Ask the user, in their language, how much ${m??"of the payment token"} they want to spend to buy ${c}. Do NOT invent an amount. Do NOT mention tool names, UI, or forms.`}}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return D(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=parseFloat(t);return Number.isFinite(n)&&n>0?t:null}requireChain(e,t){let n=typeof e.chain=="string"&&e.chain.trim()?e.chain.trim():null;return n||(typeof t?.chain=="string"&&t.chain.trim()?t.chain.trim():null)}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=D(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let c=i.data.result.find(s);if(c)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let c=a.data.find(s)??a.data[0];if(c?.token_address)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.decimals:void 0}}}async resolveSide(e,t,n,r,o="side"){let s=typeof e=="string"?e.trim():"",a=typeof t=="string"?t.trim():"";if(s.toLowerCase()==="native"||a.toLowerCase()==="native")return{address:"native",symbol:a||void 0};let i=this.normaliseAddress(e),c=a||void 0,u=m=>m.toLowerCase()===L?"native":m,d=i??c??(s||void 0);if(!d)return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,resolved:"none"}),{address:null,symbol:c};let p=await this.resolveContractAddress(d,n,r);return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,key:d,lookup:p}),p?{address:u(p.address),symbol:c??p.symbol,decimals:p.decimals}:i?{address:u(i),symbol:c}:{address:null,symbol:c}}async buildConfirmTx(e){let{userContext:t,chain:n,destContract:r,destSymbol:o,destDecimals:s,payAddr:a,paySymbol:i,payDecimals:c,payAmount:u}=e,d=t?.walletAddress;if(!d||!n||!r)return null;let p=z(n),m=Number.parseInt(n,16);if(!p||!Number.isFinite(m))return null;let h=a==="native",f=h?p.native.decimals??18:c;if(f===void 0)return null;let g=this.toRawAmount(u,f);if(!g)return null;let y=h?L:a,k=r==="native"?L:r,w;try{w=await(await ce.getServiceByProvider("debridge")).getQuote({srcChainId:m,srcTokenAddress:y,srcTokenAmount:g,dstChainId:m,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(C){return{quoteError:C instanceof Error?C.message:String(C)}}if(!w.success)return this.dbg("buildConfirmTx: quote failed",{error:w.error,errorMessage:w.errorMessage}),{quoteError:this.extractQuoteError(w)};if(!w.tx)return{quoteError:"No route available for this swap."};let b=w.tx;if(!b.to||typeof b.data!="string")return null;let T={chainId:n,to:b.to,data:b.data,value:b.value??"0",from:d},P=this.extractOutRaw(w),x=P&&s!==void 0?this.trimAmount(Number(P)/10**s):void 0,A;h||(A=await this.buildApproveTx({chain:n,walletAddress:d,payAddr:a,payDec:f,rawAmount:g,quote:w}));let S={component:"BuyTokenConfirmTx",props:{chain:{hexId:n,name:p.name},payToken:{address:h?"native":a,symbol:i,decimals:f,amount:u,rawAmount:g},buyToken:{address:r,symbol:o,decimals:s,amount:x,rawAmount:P},estimatedOut:x,estimatedOutRaw:P,provider:w.provider,swapTx:T,approveTx:A}},_=x?` They will receive about ${x} ${o}.`:"";return{ui:S,_instructions:`A confirmation panel has been opened for buying ${o} with ${u} ${i??"the selected token"}.${_} Briefly tell the user, in their language, the amount they are spending and the estimated amount they will receive, and ask them to review and confirm to complete the purchase. The estimate is approximate \u2014 say "about"/"estimated", never a guaranteed amount. NEVER invent numbers not provided here. Do NOT mention tool names, UI, forms, or internal steps like approval.`}}async derivePayAmountFromBuy(e){let{chain:t,buyAmount:n,destContract:r,payAddr:o}=e,[s,a]=await Promise.all([this.getUsdPrice(r,t),this.getUsdPrice(o,t)]);if(this.dbg("derivePayAmountFromBuy: prices",{destPrice:s,payPrice:a}),!s||!a)return null;let i=Number(n);if(!Number.isFinite(i)||i<=0)return null;let c=1.02,u=i*s/a;return!Number.isFinite(u)||u<=0?null:this.trimAmount(u*c)}async getUsdPrice(e,t){if(!this.moralis)return null;let n=e==="native"?L:e,r=await this.moralis.getTokenMetadata({address:n,chain:t}),o=r.success?r.data?.usd_price:void 0;return typeof o=="number"&&Number.isFinite(o)&&o>0?o:null}async resolvePayAmountSpec(e,t,n,r,o){let s=ue(e);if(!s)return null;if(s.kind==="token")return this.trimAmount(s.value);if(s.kind==="percent"){if(!r)return null;let i=await this.readPayBalance(r,t,n);return this.dbg("resolvePayAmountSpec: percent",{percent:s.percent,paySymbol:o,bal:i?.balanceNum}),!i||!Number.isFinite(i.balanceNum)||i.balanceNum<=0?null:s.percent>=100?this.cleanAmountString(i.balanceFormatted):this.trimAmount(i.balanceNum*s.percent/100)}let a=await this.getUsdPrice(t,n);return this.dbg("resolvePayAmountSpec: usd",{usd:s.usd,paySymbol:o,price:a}),a?this.trimAmount(s.usd/a):null}async resolveBuyAmountSpec(e,t,n,r){let o=ue(e);if(!o)return null;if(o.kind==="token")return this.trimAmount(o.value);if(o.kind==="percent"||!t)return null;let s=await this.getUsdPrice(t,n);return this.dbg("resolveBuyAmountSpec: usd",{usd:o.usd,destSymbol:r,price:s}),s?this.trimAmount(o.usd/s):null}async buildApproveTx(e){let{chain:t,walletAddress:n,payAddr:r,payDec:o,rawAmount:s,quote:a}=e;try{let c=await(await ce.getServiceByProvider("debridge")).checkApproval({chain:t,userAddress:n,tokenAddress:r,amount:s,tokenDecimals:o,quoteData:a});if(!c.isNeeded)return;let u=c.approvalData??{};if(u.to&&typeof u.data=="string")return{chainId:t,to:u.to,data:u.data,value:u.value??"0",from:n};let d=c.contractAddress;if(!d)return;let p=De({abi:fn,functionName:"approve",args:[d,BigInt(s)]});return{chainId:t,to:r,data:p,value:"0",from:n}}catch{return}}toRawAmount(e,t){let n=e.trim();if(!/^\d*\.?\d+$/.test(n))return null;let[r,o=""]=n.split("."),s=o.slice(0,t).padEnd(t,"0");try{let a=BigInt(r||"0")*10n**BigInt(t)+BigInt(s||"0");return a<=0n?null:a.toString()}catch{return null}}extractQuoteError(e){if(e.errorMessage&&e.errorMessage.trim())return e.errorMessage.trim();let t=r=>{if(typeof r=="string"&&r.trim())return r.trim();if(r&&typeof r=="object"){let o=r;for(let s of[o.message,o.errorMessage,o.error])if(typeof s=="string"&&s.trim())return s.trim()}},n=e.raw??{};return t(e.error)??t(n.errorMessage)??t(n.error)??"The swap could not be quoted right now."}extractOutRaw(e){let t=e.raw??{},n=t.tokenOut?.amount??t.tokenOut?.minAmount??t.details?.currencyOut?.amount??t.details?.currencyOut?.minimumAmount??t.estimation?.dstChainTokenOut?.recommendedAmount??t.estimation?.dstChainTokenOut?.amount;return n&&/^\d+$/.test(n)?n:void 0}async buildPayWithPicker(e){let{args:t,userContext:n,destSymbol:r,destContract:o}=e,s=this.requireChain(t,n)??void 0,a=this.moralis,i=n.walletAddress,c=await a.getWalletTokenBalances({address:i,chain:s,excludeSpam:!0,excludeUnverifiedContracts:!0}),u=c.success?c.data?.result??[]:[],d=o?.toLowerCase(),p=r.trim().toLowerCase(),m=u.filter(k=>!k.symbol||p&&k.symbol.toLowerCase()===p?!1:k.native_token?d!=="native":!(!k.token_address||d&&k.token_address.toLowerCase()===d)).sort((k,w)=>(w.usd_value??0)-(k.usd_value??0)).slice(0,8);if(m.length===0)return{error:"no_pay_token_holdings",_instructions:"The wallet has no tokens that can pay for this purchase on this chain. Tell the user to top up first. Do NOT mention tool names, UI, or forms."};let h=e.buyAmount??this.normaliseAmount(t.buy_amount),f=e.payAmount??this.normaliseAmount(t.pay_with_amount),g=typeof t.buy_with_prompt_template=="string"?t.buy_with_prompt_template:"";return{actionButtons:m.map(k=>{let w=k.symbol,b=k.name?.trim()||w,T=k.balance_formatted?`${this.cleanAmountString(k.balance_formatted)} `:"",P=typeof k.usd_value=="number"&&k.usd_value>0?` ($${k.usd_value.toFixed(2)})`:"",x=`${b}: ${T}${w}${P}`,A=h?this.buildBuyWithPrompt(g,r,w,h,null):this.buildBuyWithPrompt(g,r,w,null,f);return{label:x,prompt:A}}),_instructions:`Reply briefly in the user's language: ask them to pick which token they want to spend to buy ${r} from the options below. Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms.`}}async checkPayBalance(e){let{args:t,userContext:n,chain:r,destSymbol:o,destContract:s,payAddr:a,payAmount:i,buyAmount:c,derivedFromBuy:u}=e,d=n.walletAddress,p=await this.readPayBalance(d,a,r),m=e.paySymbol??p?.symbol??"the selected token";if(!p){this.dbg("checkPayBalance: NOT held \u2192 pay picker fallback",{paySymbol:m});let k=await this.buildPayWithPicker({args:t,userContext:n,destSymbol:o,destContract:s,buyAmount:c,payAmount:i});return"actionButtons"in k?{error:"no_pay_balance",actionButtons:k.actionButtons,_instructions:`The user wanted to spend ${m} to buy ${o}, but their wallet holds no ${m} on this chain. Tell them, in their language, that they don't have any ${m}, then say they can instead choose one of the tokens they already hold (shown below) to pay with. Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.`}:k}if(!i||e.amountSizedToBalance)return null;let h=Number(i);if(!Number.isFinite(h)||h<=p.balanceNum)return null;let f=this.trimAmount(p.balanceNum),g=this.percentSpendButtons({balanceFormatted:p.balanceFormatted,balanceNum:p.balanceNum,paySymbol:m,destSymbol:o,buyWithTemplate:typeof t.buy_with_prompt_template=="string"?t.buy_with_prompt_template:""}),y=u?`Buying ${u} ${o} needs about ${i} ${m}, but they only have ${f} ${m} on this chain \u2014 not enough. Tell them, in their language, that buying ${u} ${o} would cost about ${i} ${m}, which is more than their balance of ${f} ${m}, and ask them to buy a smaller amount or spend less \u2014 `:`The user wants to spend ${i} ${m} to buy ${o}, but they only have ${f} ${m} on this chain \u2014 not enough. Tell them, in their language, that ${i} ${m} exceeds their balance of ${f} ${m}, and ask them to pick a smaller amount \u2014 `;return{error:"insufficient_pay_balance",actionButtons:g,_instructions:y+'invite them to choose one of the options below (sized to their balance) or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT invent numbers, and do NOT mention tool names, UI, buttons, or forms.'}}async readPayBalance(e,t,n){if(!this.moralis)return null;let r=await this.moralis.getWalletTokenBalances({address:e,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[];this.dbg("readPayBalance",{payAddr:t,chain:n,ok:r.success,count:o.length,held:o.map(c=>({sym:c.symbol,addr:c.token_address,bal:c.balance_formatted}))});let s=t.toLowerCase(),a=o.find(c=>s==="native"?c.native_token===!0:c.token_address?.toLowerCase()===s);if(!a?.balance_formatted)return this.dbg("readPayBalance: pay token NOT held \u2192 null",{payAddr:t}),null;let i=Number(a.balance_formatted);if(!Number.isFinite(i)||i<=0)return this.dbg("readPayBalance: zero/invalid balance \u2192 null",{payAddr:t,balance:a.balance_formatted}),null;if(s==="native"&&n){let c=await St(n,"swap"),u=xt(i,c,n);return this.dbg("readPayBalance: native spendable",{balanceNum:i,spendable:u}),u<=0?(this.dbg("readPayBalance: native spendable \u2264 0 \u2192 null",{balanceNum:i}),null):{balanceFormatted:this.trimAmount(u),balanceNum:u,symbol:a.symbol}}return this.dbg("readPayBalance: held",{payAddr:t,balance:a.balance_formatted,symbol:a.symbol}),{balanceFormatted:a.balance_formatted,balanceNum:i,symbol:a.symbol}}percentSpendButtons(e){let{balanceFormatted:t,balanceNum:n,paySymbol:r,destSymbol:o,buyWithTemplate:s}=e;return[.25,.5,.75,1].map(i=>{let c=i===1?this.cleanAmountString(t):this.trimAmount(n*i);return{label:`${Math.round(i*100)}%`,prompt:this.buildBuyWithPrompt(s,o,r,null,c)}})}async buildAmountPicker(e){let{userContext:t,chain:n,destSymbol:r,payAddr:o,buyWithTemplate:s}=e,a=t.walletAddress,i=await this.readPayBalance(a,o,n),c=e.paySymbol??i?.symbol;if(!i||!c)return null;let u=this.percentSpendButtons({balanceFormatted:i.balanceFormatted,balanceNum:i.balanceNum,paySymbol:c,destSymbol:r,buyWithTemplate:s}),d=this.trimAmount(i.balanceNum);return{actionButtons:u,_instructions:`The user's spendable balance is ${d} ${c}. Reply briefly in the user's language: tell them their spendable ${c} balance and ask how much they want to spend to buy ${r}, inviting them to choose one of the options below or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Just ask the question. Do NOT mention tool names, UI, buttons, or forms.`}}trimAmount(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(8))):"0"}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),c=a+i,u;if(s>=0){let d=a.length+s;u=d>=c.length?c.padEnd(d,"0"):`${c.slice(0,d)}.${c.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${c}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}buildBuyPrompt(e,t){return(e&&e.includes("{token}")?e:"buy {token}").replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}buildBuyWithPrompt(e,t,n,r,o){return(e&&e.includes("{token}")&&e.includes("{pay}")?e:"buy {buy_amount} {token} with {pay_amount} {pay}").replace(/\{token\}/g,t).replace(/\{pay\}/g,n).replace(/\{buy_amount\}/g,r??"").replace(/\{pay_amount\}/g,o??"").replace(/\s+/g," ").trim()}async buildTrendingPicker(e,t){if(!this.pantograph)return{error:"trending_unavailable",_instructions:"Trending tokens are unavailable in this build (service not configured). Tell the user briefly and ask them to name a token to buy."};let n=this.requireChain(e,t)??void 0,r=typeof e.limit=="number"&&Number.isFinite(e.limit)?Math.floor(e.limit):6,o=Math.max(1,Math.min(10,r)),s=await this.pantograph.getTrendingTokens({chain:n,limit:o}),a=s.success?s.data??[]:[];if(a.length===0)return{error:"no_trending_tokens",_instructions:"No trending tokens are available for this chain right now. Tell the user briefly and suggest they name a token directly."};let i=a.filter(m=>m.symbol),c=typeof e.buy_prompt_template=="string"?e.buy_prompt_template:"",u=i.map(m=>{let h=m.symbol;return{label:h,prompt:this.buildBuyPrompt(c,h)}}),p=i.map((m,h)=>{let f=m.name?.trim()||m.symbol,g=m.symbol,y=typeof m.usdPrice=="number"&&m.usdPrice>0?`$${this.formatPrice(m.usdPrice)}`:"N/A",k=m.pricePercentChange?.["24h"],w=typeof k=="number"&&Number.isFinite(k),b=w?k>0?"\u25B2":k<0?"\u25BC":"\u25AA":"",T=w?` (${b} ${this.formatPercent(k)}%)`:"";return`${h+1}. ${f} (${g})
|
|
170
|
+
${y}${T}`}).join(`
|
|
171
|
+
-----
|
|
172
|
+
`)+`
|
|
173
|
+
---
|
|
174
|
+
Is there any token you want to buy?`;return{actionButtons:u,_instructions:`The trending tokens are on chain id "${n??"the connected chain"}" \u2014 use the human-readable chain name. Reply in the user's language. Start with a one-line header like "\u{1F4C8} Here is the list of tokens that are currently trending upwards on <chain name> chain", then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
|
|
175
|
+
|
|
176
|
+
${p}`}}formatPercent(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(6))):"0"}formatPrice(e){if(e>=1)return e.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});if(e>=.01)return e.toFixed(4);let t=e.toFixed(12),n=t.match(/^0\.0*([1-9]\d{0,3})/);return n?t.slice(0,(n[0].length||0)+0):t}};var Et=class extends v{name="open-swap-token-form";kind="action";category="wallet-action";noSuggestions=!0;moralis;pantograph;debug;constructor(e,t){super(),e!==void 0&&(this.moralis=new E(e),this.pantograph=new Y({baseUrl:e.pantographUrl})),this.debug=t?.debug??!1}dbg(e,t){this.debug&&(t===void 0?console.log(`[SwapTokenTool] ${e}`):console.log(`[SwapTokenTool] ${e}`,JSON.stringify(t)))}description='Open the SWAP TOKEN flow for ANY swap/exchange/convert intent where the user swaps a token they ALREADY HOLD into another token \u2014 "swap USDC to USDT", "swap USDC", "\u0111\u1ED5i USDC sang ETH", "convert DAI to USDC", "exchange 10 USDC for PEPE". The FROM token is the user\'s own holding (the source). Handles both cases: (a) the user named the destination \u2014 "swap USDC to USDT": pass `from_symbol` (the source, e.g. "USDC") and `to_symbol` (the destination, e.g. "USDT"). (b) the user named only the source \u2014 "swap USDC": pass `from_symbol` and leave `to_symbol` blank; the tool returns buttons of the chain\'s current trending tokens so the user picks what to receive. If the user named NO token at all \u2014 "swap" / "\u0111\u1ED5i token": leave both blank and the tool returns the wallet\'s holdings so the user picks what to swap FROM. Amount: `from_amount` = source to SPEND; `to_amount` = destination to RECEIVE. Never both.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"from_symbol",type:"string",description:`Symbol of the SOURCE token the user wants to swap FROM \u2014 a token they already hold (e.g. "USDC"). Leave EMPTY only when the user named no token at all (e.g. "swap") \u2014 the tool then returns the wallet's holdings to pick the source from.`,required:!1},{name:"from_address",type:"string",description:`Source token address (0x\u2026), OR "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from from_symbol.`,required:!1},{name:"from_amount",type:"string",description:'Quantity of the SOURCE token (the one being swapped FROM / spent) the user wants to SPEND. Set this when the amount sits next to the source token: "swap 10 USDC to USDT", "\u0111\u1ED5i 10 USDC sang ETH" \u2192 from_amount = the number next to USDC. May also be a PERCENT of the source balance ("swap 50% USDC to USDT" \u2192 "50%"), the literal word "max" meaning the WHOLE balance, or a USD value ("swap $5 USDC to USDT" \u2192 "5$"). NORMALIZE any "whole balance / everything / maximum" phrasing in ANY language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") to the literal "max"; pass plain/percent/USD numbers VERBATIM (the tool converts "%"/"$"/"max"). "max" is an AMOUNT, never a token \u2014 still set from_symbol to the source token ("ETH" in "swap max ETH to USDC"). Mutually exclusive with to_amount \u2014 set at most ONE of the two.',required:!1},{name:"to_symbol",type:"string",description:'Symbol of the DESTINATION token the user wants to RECEIVE (e.g. "USDT"). Leave EMPTY when the user did not name a destination (e.g. "swap USDC") \u2014 the tool then returns trending tokens to pick from.',required:!1},{name:"to_address",type:"string",description:`Destination token address (0x\u2026), OR "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from to_symbol.`,required:!1},{name:"to_amount",type:"string",description:`Quantity of the DESTINATION token (the one being received) the user wants to RECEIVE. Set this when the amount sits next to the destination token: "swap USDC to 5 USDT" \u2192 to_amount = the number next to USDT. May also be a USD value ("swap USDC to $5 USDT" \u2192 "5$"); pass the user's expression VERBATIM (the tool converts "$"). Mutually exclusive with from_amount \u2014 set at most ONE.`,required:!1},{name:"limit",type:"number",description:"Only used when no destination is named: max number of trending tokens to show as buttons. Default 6. Clamped to [1, 10].",required:!1},{name:"swap_from_prompt_template",type:"string",description:`A short "swap {token}" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the source picker (when the user named no token). Use the exact placeholder "{token}" (do not translate the braces) for the source symbol; put the localized verb directly in the text. English example: "swap {token}". Vietnamese example: "\u0111\u1ED5i {token}". Japanese example: "{token} \u3092\u30B9\u30EF\u30C3\u30D7". Always include {token}.`,required:!0},{name:"swap_to_prompt_template",type:"string",description:`A short "swap X to Y" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the destination (trending) picker and the percentage-spend buttons. Use these exact placeholders (do not translate the braces): "{from}" = source token, "{to}" = destination token, "{from_amount}" = quantity of the SOURCE token (place it next to {from}), "{to_amount}" = quantity of the DESTINATION token (place it next to {to}). Put the localized verb and the "to" preposition directly in the text. English example: "swap {from_amount} {from} to {to_amount} {to}". Vietnamese example: "\u0111\u1ED5i {from_amount} {from} sang {to_amount} {to}". Japanese example: "{from_amount} {from} \u3092 {to_amount} {to} \u306B\u30B9\u30EF\u30C3\u30D7". Always include {from} and {to}. Include both {from_amount} and {to_amount} too \u2014 the tool removes whichever amount is unknown (only one side ever carries an amount).`,required:!0}];async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),o&&Array.isArray(o.actionButtons)&&o.actionButtons.length>0&&(s.actionButtons=o.actionButtons),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){if(!t?.walletAddress)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before swapping. Do NOT proceed."};let r=Ie(e.chain,t);return r?{error:"wrong_chain",_instructions:`The user asked to swap on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to swap on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can swap on ${r.connectedLabel} (their current network) instead. Do NOT proceed. Do NOT mention tool names, UI, or forms.`}:this.requireChain(e,t)?this.buildResult(e,t):{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'}}async buildResult(e,t){let n=typeof e.from_symbol=="string"?e.from_symbol.trim():"",r=typeof e.from_address=="string"?e.from_address.trim():"",o=typeof e.to_symbol=="string"?e.to_symbol.trim():"",s=this.requireChain(e,t)??void 0,a=t?.walletAddress??void 0;if(this.dbg("buildResult:args",{args:e,chain:s,walletAddress:a,hasMoralis:!!this.moralis}),!n&&!r)return this.dbg("\u2192 Branch 0: source picker (no source token named)"),await this.buildSourcePicker(e,t);let i=await this.resolveSide(e.from_address,n,s,a,"from"),c=i.address,u=i.symbol??n,d=i.decimals,p=await this.resolveSide(e.to_address,o,s,a,"to"),m=p.address,h=p.symbol??o,f=p.decimals,g=await this.resolveToAmountSpec(e.to_amount,m,s,h),y=c?await this.resolveFromAmountSpec(e.from_amount,c,s,a,u):this.normaliseAmount(e.from_amount),k=ue(e.from_amount),w=!!k&&k.kind!=="token",b=null;if(this.dbg("resolved sides",{from:{fromContract:c,fromSym:u,fromDecimals:d},to:{toContract:m,toSym:h,toDecimals:f},fromAmount:y,toAmount:g}),!c)return this.dbg("\u2192 source token not resolved"),{error:"from_token_not_found",_instructions:`Could not find the token "${u||r}" to swap from on this chain. Tell the user, in their language, that the token could not be found and ask them to check the name or provide its contract address. Do NOT invent an address. Do NOT mention tool names, UI, or forms.`};if(!m&&this.moralis&&t?.walletAddress)return this.dbg("\u2192 Branch 1: destination picker (dest token missing)"),await this.buildToTrendingPicker({args:e,userContext:t,fromSym:u,fromContract:c,fromAmount:y,toAmount:g});if(!m)return{error:"to_token_missing",_instructions:`Ask the user, in their language, which token they want to receive when swapping ${u}. Do NOT invent a token. Do NOT mention tool names, UI, or forms.`};if(g&&!y){this.dbg("Branch 1.3: deriving from amount from to amount",{toAmount:g,fromSym:u,toSym:h});let T=await this.deriveFromAmountFromTo({chain:s,toAmount:g,toContract:m,toSym:h,fromContract:c,fromSym:u});if(!T)return this.dbg("\u2192 Branch 1.3: price unavailable \u2014 needs from amount"),{error:"needs_from_amount",_instructions:`Could not work out how much ${u??"of the source token"} equals ${g} ${h} right now. Tell the user, in their language, that the price couldn't be fetched at the moment, and ask how much ${u??"of the source token"} they want to swap instead. Do NOT invent an amount or a price. Do NOT mention tool names, UI, or forms.`};y=T,b=g,w=!1,this.dbg("\u2192 Branch 1.3: derived from amount",{fromAmount:y,derivedFromTo:b})}if(this.moralis&&t?.walletAddress){this.dbg("Branch 1.4: checking source balance",{fromContract:c,fromSym:u,fromAmount:y});let T=await this.checkFromBalance({args:e,userContext:t,chain:s,fromContract:c,fromSym:u,fromAmount:y,toAmount:g,derivedFromTo:b,amountSizedToBalance:w});if(T)return this.dbg("\u2192 Branch 1.4: balance shortfall \u2014 returning",{error:T.error,hasButtons:Array.isArray(T.actionButtons)}),T;this.dbg("Branch 1.4: balance OK \u2014 continuing")}if(!y&&!g&&this.moralis&&t?.walletAddress){this.dbg("Branch 1.5: building amount picker");let T=await this.buildAmountPicker({userContext:t,chain:s,fromContract:c,fromSym:u,toSym:h,swapToTemplate:typeof e.swap_to_prompt_template=="string"?e.swap_to_prompt_template:""});if(T)return this.dbg("\u2192 Branch 1.5: amount picker returned"),T;this.dbg("Branch 1.5: amount picker null \u2014 needs amount")}if(y&&(d!==void 0||c==="native")){this.dbg("Branch 1.75: building confirm tx");let T=await this.buildConfirmTx({userContext:t,chain:s,toContract:m,toSym:h,toDecimals:f,fromContract:c,fromSym:u,fromDecimals:d,fromAmount:y});return T&&"ui"in T?(this.dbg("\u2192 Branch 1.75: confirm tx returned"),T):T&&"quoteError"in T?(this.dbg("\u2192 Branch 1.75: quote error from provider",{quoteError:T.quoteError}),{error:"quote_failed",quoteError:T.quoteError,_instructions:`Could not quote swapping ${u??"the source token"} to ${h}. The swap provider reported: "${T.quoteError}". Tell the user, in their language, exactly that reason (translate the wording, keep any numbers/limits verbatim) and suggest they adjust the amount or try again shortly. Do NOT invent numbers or a different reason. Do NOT mention tool names, UI, or forms.`}):(this.dbg("Branch 1.75: confirm tx unavailable (null)"),{error:"quote_failed",_instructions:`Could not get a quote to swap ${u??"the source token"} to ${h} right now. Tell the user, in their language, that the swap could not be quoted at the moment and ask them to try again shortly or with a different amount. Do NOT invent numbers. Do NOT mention tool names, UI, or forms.`})}return this.dbg("\u2192 needs from amount"),{error:"needs_from_amount",_instructions:`Ask the user, in their language, how much ${u??"of the source token"} they want to swap to ${h}. Do NOT invent an amount. Do NOT mention tool names, UI, or forms.`}}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return D(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=parseFloat(t);return Number.isFinite(n)&&n>0?t:null}requireChain(e,t){let n=typeof e.chain=="string"&&e.chain.trim()?e.chain.trim():null;return n||(typeof t?.chain=="string"&&t.chain.trim()?t.chain.trim():null)}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=D(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let c=i.data.result.find(s);if(c)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let c=a.data.find(s)??a.data[0];if(c?.token_address)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.decimals:void 0}}}async resolveSide(e,t,n,r,o="side"){let s=typeof e=="string"?e.trim():"",a=typeof t=="string"?t.trim():"";if(s.toLowerCase()==="native"||a.toLowerCase()==="native")return{address:"native",symbol:a||void 0};let i=this.normaliseAddress(e),c=a||void 0,u=m=>m.toLowerCase()===L?"native":m,d=i??c??(s||void 0);if(!d)return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,resolved:"none"}),{address:null,symbol:c};let p=await this.resolveContractAddress(d,n,r);return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,key:d,lookup:p}),p?{address:u(p.address),symbol:c??p.symbol,decimals:p.decimals}:i?{address:u(i),symbol:c}:{address:null,symbol:c}}async buildConfirmTx(e){let{userContext:t,chain:n,toContract:r,toSym:o,toDecimals:s,fromContract:a,fromSym:i,fromDecimals:c,fromAmount:u}=e,d=t?.walletAddress;if(!d||!n||!r)return null;let p=z(n),m=Number.parseInt(n,16);if(!p||!Number.isFinite(m))return null;let h=a==="native",f=h?p.native.decimals??18:c;if(f===void 0)return null;let g=this.toRawAmount(u,f);if(!g)return null;let y=h?L:a,k=r==="native"?L:r,w;try{w=await(await ce.getServiceByProvider("debridge")).getQuote({srcChainId:m,srcTokenAddress:y,srcTokenAmount:g,dstChainId:m,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(C){return{quoteError:C instanceof Error?C.message:String(C)}}if(!w.success)return this.dbg("buildConfirmTx: quote failed",{error:w.error,errorMessage:w.errorMessage}),{quoteError:this.extractQuoteError(w)};if(!w.tx)return{quoteError:"No route available for this swap."};let b=w.tx;if(!b.to||typeof b.data!="string")return null;let T={chainId:n,to:b.to,data:b.data,value:b.value??"0",from:d},P=this.extractOutRaw(w),x=P&&s!==void 0?this.trimAmount(Number(P)/10**s):void 0,A;h||(A=await this.buildApproveTx({chain:n,walletAddress:d,fromAddr:a,fromDec:f,rawAmount:g,quote:w}));let S={component:"SwapTokenConfirmTx",props:{chain:{hexId:n,name:p.name},fromToken:{address:h?"native":a,symbol:i,decimals:f,amount:u,rawAmount:g},toToken:{address:r,symbol:o,decimals:s,amount:x,rawAmount:P},estimatedOut:x,estimatedOutRaw:P,provider:w.provider,swapTx:T,approveTx:A}},_=x?` They will receive about ${x} ${o}.`:"";return{ui:S,_instructions:`A confirmation panel has been opened for swapping ${u} ${i??"the source token"} to ${o}.${_} Briefly tell the user, in their language, the amount they are swapping and the estimated amount they will receive, and ask them to review and confirm to complete the swap. The estimate is approximate \u2014 say "about"/"estimated", never a guaranteed amount. NEVER invent numbers not provided here. Do NOT mention tool names, UI, forms, or internal steps like approval.`}}async deriveFromAmountFromTo(e){let{chain:t,toAmount:n,toContract:r,fromContract:o}=e,[s,a]=await Promise.all([this.getUsdPrice(r,t),this.getUsdPrice(o,t)]);if(this.dbg("deriveFromAmountFromTo: prices",{toPrice:s,fromPrice:a}),!s||!a)return null;let i=Number(n);if(!Number.isFinite(i)||i<=0)return null;let c=1.02,u=i*s/a;return!Number.isFinite(u)||u<=0?null:this.trimAmount(u*c)}async getUsdPrice(e,t){if(!this.moralis)return null;let n=e==="native"?L:e,r=await this.moralis.getTokenMetadata({address:n,chain:t}),o=r.success?r.data?.usd_price:void 0;return typeof o=="number"&&Number.isFinite(o)&&o>0?o:null}async resolveFromAmountSpec(e,t,n,r,o){let s=ue(e);if(!s)return null;if(s.kind==="token")return this.trimAmount(s.value);if(s.kind==="percent"){if(!r)return null;let i=await this.readFromBalance(r,t,n);return this.dbg("resolveFromAmountSpec: percent",{percent:s.percent,fromSym:o,bal:i?.balanceNum}),!i||!Number.isFinite(i.balanceNum)||i.balanceNum<=0?null:s.percent>=100?this.cleanAmountString(i.balanceFormatted):this.trimAmount(i.balanceNum*s.percent/100)}let a=await this.getUsdPrice(t,n);return this.dbg("resolveFromAmountSpec: usd",{usd:s.usd,fromSym:o,price:a}),a?this.trimAmount(s.usd/a):null}async resolveToAmountSpec(e,t,n,r){let o=ue(e);if(!o)return null;if(o.kind==="token")return this.trimAmount(o.value);if(o.kind==="percent"||!t)return null;let s=await this.getUsdPrice(t,n);return this.dbg("resolveToAmountSpec: usd",{usd:o.usd,toSym:r,price:s}),s?this.trimAmount(o.usd/s):null}async buildApproveTx(e){let{chain:t,walletAddress:n,fromAddr:r,fromDec:o,rawAmount:s,quote:a}=e;try{let c=await(await ce.getServiceByProvider("debridge")).checkApproval({chain:t,userAddress:n,tokenAddress:r,amount:s,tokenDecimals:o,quoteData:a});if(!c.isNeeded)return;let u=c.approvalData??{};if(u.to&&typeof u.data=="string")return{chainId:t,to:u.to,data:u.data,value:u.value??"0",from:n};let d=c.contractAddress;if(!d)return;let p=De({abi:fn,functionName:"approve",args:[d,BigInt(s)]});return{chainId:t,to:r,data:p,value:"0",from:n}}catch{return}}toRawAmount(e,t){let n=e.trim();if(!/^\d*\.?\d+$/.test(n))return null;let[r,o=""]=n.split("."),s=o.slice(0,t).padEnd(t,"0");try{let a=BigInt(r||"0")*10n**BigInt(t)+BigInt(s||"0");return a<=0n?null:a.toString()}catch{return null}}extractQuoteError(e){if(e.errorMessage&&e.errorMessage.trim())return e.errorMessage.trim();let t=r=>{if(typeof r=="string"&&r.trim())return r.trim();if(r&&typeof r=="object"){let o=r;for(let s of[o.message,o.errorMessage,o.error])if(typeof s=="string"&&s.trim())return s.trim()}},n=e.raw??{};return t(e.error)??t(n.errorMessage)??t(n.error)??"The swap could not be quoted right now."}extractOutRaw(e){let t=e.raw??{},n=t.tokenOut?.amount??t.tokenOut?.minAmount??t.details?.currencyOut?.amount??t.details?.currencyOut?.minimumAmount??t.estimation?.dstChainTokenOut?.recommendedAmount??t.estimation?.dstChainTokenOut?.amount;return n&&/^\d+$/.test(n)?n:void 0}async buildSourcePicker(e,t){if(!this.moralis||!t?.walletAddress)return{error:"source_unavailable",_instructions:"Cannot list the wallet holdings in this build. Tell the user briefly and ask them to name the token they want to swap from."};let n=this.requireChain(e,t)??void 0,r=t.walletAddress,o=await this.moralis.getWalletTokenBalances({address:r,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),a=(o.success?o.data?.result??[]:[]).filter(u=>u.symbol?u.native_token?!0:!!u.token_address:!1).sort((u,d)=>(d.usd_value??0)-(u.usd_value??0)).slice(0,8);if(a.length===0)return{error:"no_swap_holdings",_instructions:"The wallet holds no tokens to swap on this chain. Tell the user to top up first. Do NOT mention tool names, UI, or forms."};let i=typeof e.swap_from_prompt_template=="string"?e.swap_from_prompt_template:"";return{actionButtons:a.map(u=>{let d=u.symbol,p=u.name?.trim()||d,m=u.balance_formatted?`${this.cleanAmountString(u.balance_formatted)} `:"",h=typeof u.usd_value=="number"&&u.usd_value>0?` ($${u.usd_value.toFixed(2)})`:"";return{label:`${p}: ${m}${d}${h}`,prompt:this.buildSwapFromPrompt(i,d)}}),_instructions:"Reply briefly in the user's language: ask them to pick which token they want to swap from the options below. Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms."}}async buildToTrendingPicker(e){let{args:t,userContext:n,fromSym:r,fromContract:o}=e,s=this.requireChain(t,n)??void 0,a=this.pantograph,i=typeof t.limit=="number"&&Number.isFinite(t.limit)?Math.floor(t.limit):6,c=Math.max(1,Math.min(10,i)),u=await a.getTrendingTokens({chain:s,limit:c+2}),d=u.success?u.data??[]:[],p=o?.toLowerCase(),m=r.toLowerCase(),h=d.filter(T=>!(!T.symbol||p&&p!=="native"&&T.tokenAddress&&T.tokenAddress.toLowerCase()===p||T.symbol.toLowerCase()===m)).slice(0,c);if(h.length===0)return{error:"no_trending_tokens",_instructions:`No trending tokens are available to swap ${r} into on this chain right now. Tell the user briefly and suggest they name the token they want to receive directly. Do NOT mention tool names, UI, or forms.`};let f=e.fromAmount??this.normaliseAmount(t.from_amount),g=e.toAmount??this.normaliseAmount(t.to_amount),y=typeof t.swap_to_prompt_template=="string"?t.swap_to_prompt_template:"",k=h.map(T=>{let P=T.symbol,x=f?this.buildSwapToPrompt(y,r,P,f,null):this.buildSwapToPrompt(y,r,P,null,g);return{label:P,prompt:x}}),b=h.map((T,P)=>{let x=T.name?.trim()||T.symbol,A=T.symbol,I=typeof T.usdPrice=="number"&&T.usdPrice>0?`$${this.formatPrice(T.usdPrice)}`:"N/A",S=T.pricePercentChange?.["24h"],_=typeof S=="number"&&Number.isFinite(S),C=_?S>0?"\u25B2":S<0?"\u25BC":"\u25AA":"",q=_?` (${C} ${this.formatPercent(S)}%)`:"";return`${P+1}. ${x} (${A})
|
|
177
|
+
${I}${q}`}).join(`
|
|
178
|
+
-----
|
|
179
|
+
`)+`
|
|
180
|
+
---
|
|
181
|
+
Which token do you want to receive?`;return{actionButtons:k,_instructions:`The user wants to swap ${r} into another token. The trending tokens are on chain id "${s??"the connected chain"}" \u2014 use the human-readable chain name. Reply in the user's language. Start with a one-line header like "\u{1F4C8} Here are tokens currently trending on <chain name> that you can swap ${r} into", then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
|
|
182
|
+
|
|
183
|
+
${b}`}}async checkFromBalance(e){let{args:t,userContext:n,chain:r,fromContract:o,fromAmount:s,derivedFromTo:a}=e,i=n.walletAddress,c=await this.readFromBalance(i,o,r),u=e.fromSym??c?.symbol??"the selected token";if(!c){this.dbg("checkFromBalance: NOT held \u2192 source picker fallback",{fromSym:u});let f=await this.buildSourcePicker(t,n);return"actionButtons"in f?{error:"no_from_balance",actionButtons:f.actionButtons,_instructions:`The user wanted to swap ${u}, but their wallet holds no ${u} on this chain. Tell them, in their language, that they don't have any ${u}, then say they can instead choose one of the tokens they already hold (shown below) to swap from. Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.`}:f}if(!s||e.amountSizedToBalance)return null;let d=Number(s);if(!Number.isFinite(d)||d<=c.balanceNum)return null;let p=this.trimAmount(c.balanceNum),m=this.percentSpendButtons({balanceFormatted:c.balanceFormatted,balanceNum:c.balanceNum,fromSym:u,toSym:typeof t.to_symbol=="string"?t.to_symbol.trim():c.symbol??"the destination token",swapToTemplate:typeof t.swap_to_prompt_template=="string"?t.swap_to_prompt_template:""}),h=a?`Receiving ${a} of the destination token needs about ${s} ${u}, but they only have ${p} ${u} on this chain \u2014 not enough. Tell them, in their language, that this would cost about ${s} ${u}, which is more than their balance of ${p} ${u}, and ask them to swap a smaller amount \u2014 `:`The user wants to swap ${s} ${u}, but they only have ${p} ${u} on this chain \u2014 not enough. Tell them, in their language, that ${s} ${u} exceeds their balance of ${p} ${u}, and ask them to pick a smaller amount \u2014 `;return{error:"insufficient_from_balance",actionButtons:m,_instructions:h+'invite them to choose one of the options below (sized to their balance) or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT invent numbers, and do NOT mention tool names, UI, buttons, or forms.'}}async readFromBalance(e,t,n){if(!this.moralis)return null;let r=await this.moralis.getWalletTokenBalances({address:e,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[];this.dbg("readFromBalance",{fromAddr:t,chain:n,ok:r.success,count:o.length});let s=t.toLowerCase(),a=o.find(c=>s==="native"?c.native_token===!0:c.token_address?.toLowerCase()===s);if(!a?.balance_formatted)return this.dbg("readFromBalance: source token NOT held \u2192 null",{fromAddr:t}),null;let i=Number(a.balance_formatted);if(!Number.isFinite(i)||i<=0)return this.dbg("readFromBalance: zero/invalid balance \u2192 null",{fromAddr:t,balance:a.balance_formatted}),null;if(s==="native"&&n){let c=await St(n,"swap"),u=xt(i,c,n);return this.dbg("readFromBalance: native spendable",{balanceNum:i,spendable:u}),u<=0?(this.dbg("readFromBalance: native spendable \u2264 0 \u2192 null",{balanceNum:i}),null):{balanceFormatted:this.trimAmount(u),balanceNum:u,symbol:a.symbol}}return this.dbg("readFromBalance: held",{fromAddr:t,balance:a.balance_formatted,symbol:a.symbol}),{balanceFormatted:a.balance_formatted,balanceNum:i,symbol:a.symbol}}percentSpendButtons(e){let{balanceFormatted:t,balanceNum:n,fromSym:r,toSym:o,swapToTemplate:s}=e;return[.25,.5,.75,1].map(i=>{let c=i===1?this.cleanAmountString(t):this.trimAmount(n*i);return{label:`${Math.round(i*100)}%`,prompt:this.buildSwapToPrompt(s,r,o,c,null)}})}async buildAmountPicker(e){let{userContext:t,chain:n,fromContract:r,toSym:o,swapToTemplate:s}=e,a=t.walletAddress,i=await this.readFromBalance(a,r,n),c=e.fromSym??i?.symbol;if(!i||!c)return null;let u=this.percentSpendButtons({balanceFormatted:i.balanceFormatted,balanceNum:i.balanceNum,fromSym:c,toSym:o,swapToTemplate:s}),d=this.trimAmount(i.balanceNum);return{actionButtons:u,_instructions:`The user's spendable balance is ${d} ${c}. Reply briefly in the user's language: tell them their spendable ${c} balance and ask how much they want to swap to ${o}, inviting them to choose one of the options below or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Just ask the question. Do NOT mention tool names, UI, buttons, or forms.`}}trimAmount(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(8))):"0"}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),c=a+i,u;if(s>=0){let d=a.length+s;u=d>=c.length?c.padEnd(d,"0"):`${c.slice(0,d)}.${c.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${c}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}buildSwapFromPrompt(e,t){return(e&&e.includes("{token}")?e:"swap {token}").replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}buildSwapToPrompt(e,t,n,r,o){return(e&&e.includes("{from}")&&e.includes("{to}")?e:"swap {from_amount} {from} to {to_amount} {to}").replace(/\{from\}/g,t).replace(/\{to\}/g,n).replace(/\{from_amount\}/g,r??"").replace(/\{to_amount\}/g,o??"").replace(/\s+/g," ").trim()}formatPercent(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(6))):"0"}formatPrice(e){if(e>=1)return e.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});if(e>=.01)return e.toFixed(4);let t=e.toFixed(12),n=t.match(/^0\.0*([1-9]\d{0,3})/);return n?t.slice(0,(n[0].length||0)+0):t}};var Nt=class{messages=[];fullMessages=[];summary=null;maxMessages;constructor(e=50){this.maxMessages=e}add(e){this.messages.push(e),this.fullMessages.push(e)}addMany(e){this.messages.push(...e),this.fullMessages.push(...e)}addUIOnly(e){this.fullMessages.push(e)}rollbackLastWorkingMessage(){this.messages.pop()}getAll(){return[...this.messages]}getAllFull(){return[...this.fullMessages]}getRecent(e){return this.messages.slice(-e)}getConversation(){let e=[];return this.summary&&e.push({role:"system",content:`[Previous conversation summary]: ${this.summary}`,timestamp:0}),e.push(...this.messages),e}get length(){return this.messages.length}needsSummary(){return this.messages.filter(t=>t.role==="user").length>this.maxMessages}getSummary(){return this.summary}compact(e,t=10){this.summary=e,this.messages=this.messages.slice(-t)}compactWith(e,t){this.summary=e,this.messages=[...t]}clear(){this.messages=[],this.fullMessages=[],this.summary=null}serialise(){return{messages:[...this.messages],summary:this.summary,fullMessages:[...this.fullMessages]}}restore(e){Array.isArray(e)?(this.messages=[...e],this.fullMessages=[...e],this.summary=null):(this.messages=[...e.messages],this.summary=e.summary??null,this.fullMessages=e.fullMessages?[...e.fullMessages]:[...e.messages])}};var It=class{llm;constructor(e){this.llm=e}async summarize(e){let t=e.filter(s=>s.role==="user"||s.role==="assistant");if(t.length===0)return"";let n=t.map(s=>`${s.role==="user"?"User":"Assistant"}: ${s.content}`).join(`
|
|
184
|
+
`),r=[{role:"system",content:"You are a conversation summarizer. Produce a concise summary of the conversation below. Keep key facts, decisions, and context that would be needed to continue the conversation. Reply with only the summary, no extra commentary. Respond in the same language as the conversation.",timestamp:0},{role:"user",content:`Summarise this conversation:
|
|
185
|
+
|
|
186
|
+
${n}`,timestamp:Date.now()}];return(await this.llm.chat(r)).text}};var Us=`You are a strict FAQ matcher. Given a user query and a list of FAQ entries, determine which FAQ entries the user is SPECIFICALLY asking about.
|
|
187
|
+
|
|
188
|
+
RULES:
|
|
189
|
+
- Only match when the user's question is CLEARLY about the same topic as the FAQ entry.
|
|
190
|
+
- Do NOT match loosely related topics. For example, if the user asks about "ETH price" and a FAQ is about "What is staking?", that is NOT a match \u2014 they are different topics.
|
|
191
|
+
- Match by MEANING, not exact words. The user may ask in any language or use synonyms.
|
|
192
|
+
- Return a JSON array of objects: [{"index": <0-based index>, "score": <0.0 to 1.0>}]
|
|
193
|
+
- score >= 0.8 = the user is asking essentially the same question as the FAQ
|
|
194
|
+
- score 0.6-0.8 = the FAQ directly addresses the user's question
|
|
195
|
+
- score < 0.6 = NOT a match, do NOT include
|
|
196
|
+
- If no entries match, return an empty array: []
|
|
197
|
+
- When in doubt, do NOT match. Precision is more important than recall.
|
|
198
|
+
- Return ONLY the JSON array, no other text.
|
|
199
|
+
|
|
200
|
+
FAQ ENTRIES:
|
|
201
|
+
`,Dt=class{entries=[];llm=null;constructor(e){e&&(this.entries=e)}setLLM(e){this.llm=e}setEntries(e){this.entries=e}addEntries(e){this.entries.push(...e)}getEntries(){return this.entries.slice()}get size(){return this.entries.length}async search(e,t=3){return this.entries.length===0?[]:this.llm?this.llmSearch(e,t):this.tokenSearch(e,t,.25)}async llmSearch(e,t){let n=this.entries.map((o,s)=>`[${s}] ${o.question}`).join(`
|
|
202
|
+
`),r=[{role:"user",content:Us+n+`
|
|
203
|
+
|
|
204
|
+
USER QUERY: ${e}`,timestamp:0}];try{let a=(await this.llm.chat(r)).text.trim().match(/\[[\s\S]*\]/);if(!a)return[];let i=JSON.parse(a[0]),c=[];for(let u of i)u.index>=0&&u.index<this.entries.length&&u.score>=.6&&c.push({entry:this.entries[u.index],score:u.score});return c.sort((u,d)=>d.score-u.score),c.slice(0,t)}catch{return this.tokenSearch(e,t,.25)}}tokenSearch(e,t,n){let r=this.tokenize(e);if(r.size===0)return[];let o=[];for(let s of this.entries){let a=this.tokenize(s.question),i=this.jaccardSimilarity(r,a);i>=n&&o.push({entry:s,score:i})}return o.sort((s,a)=>a.score-s.score),o.slice(0,t)}tokenize(e){let t=e.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu," ").split(/\s+/).filter(n=>n.length>1);return new Set(t)}jaccardSimilarity(e,t){if(e.size===0||t.size===0)return 0;let n=0;for(let o of e)t.has(o)&&n++;let r=e.size+t.size-n;return r===0?0:n/r}};var Kt=require("@upstash/vector"),Rs="https://perfect-octopus-59684-us1-vector.upstash.io",Es="ABkFMHBlcmZlY3Qtb2N0b3B1cy01OTY4NC11czFhZG1pbk56WXdOV0k0T0RndFpUaGtOeTAwWWpjMkxXRmtNRFV0WW1WaU9UaGtOMlV5T0ROaQ==",Se=class{index;namespace;minScore;defaultTopK;embedStrategy;fusionAlgorithm;debug;constructor(e={}){let t=e.url??(typeof process<"u"?process.env.UPSTASH_VECTOR_REST_URL:void 0)??Rs,n=e.token??(typeof process<"u"?process.env.UPSTASH_VECTOR_REST_TOKEN:void 0)??Es;this.index=new Kt.Index({url:t,token:n}),this.namespace=e.namespace,this.minScore=e.minScore??1.3,this.defaultTopK=e.defaultTopK??3,this.embedStrategy=e.embedStrategy??"question",this.fusionAlgorithm=e.fusionAlgorithm==="RRF"?Kt.FusionAlgorithm.RRF:Kt.FusionAlgorithm.DBSF,this.debug=e.debug??!1}async search(e,t){let n=t??this.defaultTopK;if(!e.trim())return[];let r=this.namespace?this.index.namespace(this.namespace):this.index,o;try{o=await r.query({data:e,topK:n,includeMetadata:!0,fusionAlgorithm:this.fusionAlgorithm})}catch(a){return this.debug&&console.warn("[UpstashKnowledgeBase] query failed:",a),[]}let s=[];for(let a of o){if(a.score<this.minScore)continue;let i=a.metadata;!i?.question||!i?.answer||s.push({entry:{question:i.question,answer:i.answer},score:a.score})}return this.debug&&console.log(`[UpstashKnowledgeBase] query="${e}" \u2192 ${s.length}/${o.length} hit(s) above ${this.minScore}`+(s[0]?` (top: ${s[0].score.toFixed(3)})`:"")),s}async upsert(e){if(e.length===0)return{count:0};let t=this.namespace?this.index.namespace(this.namespace):this.index,n=await Promise.all(e.map(async r=>({id:r.id??await this.deriveId(r.question),data:this.buildEmbedText(r),metadata:{question:r.question,answer:r.answer,...r.metadata??{}}})));return await t.upsert(n),this.debug&&console.log(`[UpstashKnowledgeBase] upserted ${n.length} entr${n.length===1?"y":"ies"} (strategy=${this.embedStrategy})`),{count:n.length}}buildEmbedText(e){return this.embedStrategy==="question-and-answer"?`${e.question}
|
|
205
|
+
|
|
206
|
+
${e.answer}`:e.question}async delete(e){return e.length===0?{deleted:0}:{deleted:(await(this.namespace?this.index.namespace(this.namespace):this.index).delete(e)).deleted}}async reset(){this.namespace?await this.index.reset({namespace:this.namespace}):await this.index.reset()}async size(){let e=await this.index.info();return this.namespace?e.namespaces?.[this.namespace]?.vectorCount??0:e.vectorCount??0}async deriveId(e){let t=new TextEncoder().encode(e.trim().toLowerCase()),n=await globalThis.crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(n)).map(r=>r.toString(16).padStart(2,"0")).join("").slice(0,32)}};function de(l){return l.filter(e=>e.role!=="tool"&&!(e.role==="assistant"&&e.toolCalls&&e.toolCalls.length>0)&&!e._intermediate)}function Ro(l,e){let t=[];for(let n=0;n<l.length;n++){let r=l[n];if(r.role==="assistant"&&r.toolCalls&&r.toolCalls.length>0){if(r.toolCalls.every(i=>e.has(i.toolName))){t.push(r);continue}let s=r.toolCalls.map(i=>i.toolName).join(", ");t.push({role:"assistant",content:`(Previously called external tools: ${s} \u2014 results not visible here.)`,timestamp:r.timestamp});let a=new Set(r.toolCalls.map(i=>i.callId));for(;n+1<l.length;){let i=l[n+1];if(i.role==="tool"&&i.toolCallId&&a.has(i.toolCallId))n++;else break}continue}if(r.role==="tool"){r.toolName&&e.has(r.toolName)&&t.push(r);continue}t.push(r)}return t}function ye(l,e){if(l.length<=e)return[...l];let t=l.length-e;for(;t>0;){if(l[t].role==="tool"){t--;continue}break}let n=l.slice(t),r=0;for(;r<n.length&&n[r].role==="tool";)r++;return n.slice(r)}function Eo(l,e){return ye(l,e)}function No(l){for(let e=l.length-1;e>=0;e--){let t=l[e];if(t.role==="assistant"&&!(t.toolCalls&&t.toolCalls.length>0))return t.subagents&&t.subagents.length>0?[...t.subagents]:[]}return[]}function Io(l,e=5){let t=l.filter(s=>s.role==="tool").slice(-e);if(t.length===0)return"";let n=[],r=[];for(let s of t){let a;try{a=JSON.parse(s.content)}catch{continue}if(typeof a!="object"||a===null)continue;let i=a,c=i.pagination;c&&typeof c.cursor=="string"&&c.cursor&&r.push({toolName:s.toolName??"tool",cursor:c.cursor});let u=i.transactions??i.result??i.transfers;if(Array.isArray(u))for(let d of u)typeof d.hash=="string"&&d.hash.startsWith("0x")?n.push(d.hash):typeof d.transaction_hash=="string"&&d.transaction_hash.startsWith("0x")&&n.push(d.transaction_hash)}if(n.length===0&&r.length===0)return"";let o=["[Tool data available from previous calls:]"];if(n.length>0&&o.push(`Transaction hashes (use EXACT values): ${n.slice(0,10).join(", ")}`),r.length>0){let s=r[r.length-1];o.push(`Pagination cursor from ${s.toolName}: ${s.cursor}`)}return o.join(`
|
|
207
|
+
`)}var Ns=`You are a ROUTER.
|
|
208
|
+
|
|
209
|
+
The incoming user query has ALREADY been contextualized upstream \u2014 it is a
|
|
210
|
+
self-contained, standalone question. Your ONLY job: decide which subagent(s)
|
|
211
|
+
to call. You MUST NOT answer the user \u2014 output JSON only.
|
|
212
|
+
|
|
213
|
+
## OUTPUT FORMAT
|
|
214
|
+
|
|
215
|
+
{
|
|
216
|
+
"analysis": "<one-line intent summary in user's language>",
|
|
217
|
+
"assignments": [
|
|
218
|
+
{
|
|
219
|
+
"subagent": "<exact subagent name>",
|
|
220
|
+
"query": "<focused sub-query for that subagent, user's language>",
|
|
221
|
+
"reasoning": "<one-line reason>"
|
|
10
222
|
}
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
## HOW TO DECIDE
|
|
227
|
+
|
|
228
|
+
Read each subagent's description carefully \u2014 they fully define what each agent handles and what it does NOT handle.
|
|
229
|
+
Apply this priority order:
|
|
230
|
+
|
|
231
|
+
1. Does the query match a specific subagent's described scope? \u2192 assign it.
|
|
232
|
+
2. Does it span multiple subagent scopes? \u2192 emit MULTIPLE assignments (they run in PARALLEL).
|
|
233
|
+
3. No specific subagent matches BUT the query is about blockchain / crypto / token / NFT / wallet / DeFi / on-chain data / smart contracts / any supported EVM chain \u2192 assign "ai-agent" with the user's original query.
|
|
234
|
+
4. Only return "assignments": [] for pure general knowledge, small-talk, or clearly non-crypto queries.
|
|
235
|
+
|
|
236
|
+
NOTE: wallet-agent / token-agent / nft-agent each have an internal AI Cortex fallback,
|
|
237
|
+
so it is safe to route a wallet/token/NFT-shaped query to its matching domain agent even
|
|
238
|
+
if no specific structured tool obviously fits \u2014 the agent will fall back to Cortex on its own.
|
|
239
|
+
|
|
240
|
+
## HARD RULES
|
|
241
|
+
|
|
242
|
+
- ONLY use subagent names from the provided list. NEVER invent names.
|
|
243
|
+
- Keep each "query" focused and concise \u2014 do not copy the full user message if only part belongs to that subagent.
|
|
244
|
+
- Do NOT include tool names, chain ids, or implementation details in the query field.
|
|
245
|
+
- Match the user's language in all fields.`,Lt=class{llm;historyMessages;debug;constructor(e,t){this.llm=e,this.historyMessages=t?.historyMessages??10,this.debug=t?.debug??!1}async route(e,t,n,r,o=[]){let s=n.map(b=>`### ${b.name} (domain: ${b.domain})
|
|
246
|
+
${b.description}`).join(`
|
|
247
|
+
|
|
248
|
+
`),a=[];r?.walletAddress&&a.push(`Connected wallet: ${r.walletAddress}`),r?.chain&&a.push(`Current chain: ${r.chain}`);let i=a.length>0?`
|
|
249
|
+
|
|
250
|
+
User context:
|
|
251
|
+
${a.join(`
|
|
252
|
+
`)}`:"",c=o.length>0?`
|
|
253
|
+
|
|
254
|
+
Previous turn handled by: ${o.join(", ")}. If this query is a natural continuation (same topic), prefer the same subagent(s). Override only if the query clearly belongs to a different domain.`:"",u=de(t),d=ye(u,this.historyMessages);this.debug&&(console.log(`
|
|
255
|
+
[Router] history window (${d.length} msg${d.length===1?"":"s"}):`),console.log(`[Router] user query: "${e}"`));let p=[{role:"system",content:Ns,timestamp:0},...d,{role:"user",content:`Available subagents:
|
|
256
|
+
${s}${i}${c}
|
|
257
|
+
|
|
258
|
+
User query: ${e}`,timestamp:Date.now()}],m=Date.now(),h=null,f="",g=null;for(let b=1;b<=2;b++){let T=await this.llm.chat(p);f=T.text;try{h=this.parseJSON(T.text);break}catch(P){g=P,b<2&&(this.debug&&console.log(`[Router] JSON parse failed (attempt ${b}) \u2014 re-prompting for JSON`),p.push({role:"assistant",content:T.text||"",timestamp:Date.now()}),p.push({role:"user",content:'That response was not valid JSON. Do NOT answer the user and do NOT ask any questions. Reply with ONLY the routing JSON object ({"analysis": "...", "assignments": [...]}) and nothing else.',timestamp:Date.now()}))}}let y=Date.now()-m;if(!h)return this.debug&&(console.log(`[Router] JSON parse failed after retry: ${g}`),console.log(`[Router] raw response: ${f.slice(0,300)}`)),{analysis:"Router could not parse response.",assignments:[]};let k=new Set(n.map(b=>b.name)),w=Array.isArray(h.assignments)?h.assignments.filter(b=>!!b&&typeof b.subagent=="string").filter(b=>k.has(b.subagent)).map(b=>({subagent:b.subagent,query:typeof b.query=="string"&&b.query?b.query:e,reasoning:typeof b.reasoning=="string"?b.reasoning:""})):[];if(this.debug){let b=w.length>0?w.map(T=>T.subagent).join(", "):"(none \u2014 direct answer)";if(console.log(`
|
|
259
|
+
[Router] (${y}ms)`),console.log(` analysis : ${h.analysis??""}`),console.log(` dispatch : ${b}`),w.length>0)for(let T of w)console.log(` \u2192 ${T.subagent}: "${T.query}"`),console.log(` reason: ${T.reasoning}`)}return{analysis:typeof h.analysis=="string"?h.analysis:"",assignments:w}}async dispatch(e,t,n,r){if(e.assignments.length===0)return[];this.debug&&console.log(`
|
|
260
|
+
[Router] dispatching ${e.assignments.length} subagent(s) in parallel\u2026`);let o=Date.now(),s=e.assignments.map(async i=>{let c=t.get(i.subagent);if(!c)return this.debug&&console.log(`[Router] WARN: subagent "${i.subagent}" not registered`),{subagentName:i.subagent,steps:[],finalAnswer:`Subagent "${i.subagent}" is not registered.`,toolResults:[],toolMessages:[]};let u=Date.now(),d=await c.run({query:i.query,reasoning:i.reasoning},n,r);if(this.debug){let p=d.toolResults.map(m=>m.toolName).join(", ")||"\u2014";console.log(`[Router] ${i.subagent} done in ${Date.now()-u}ms | tools: ${p}`)}return d}),a=await Promise.all(s);return this.debug&&console.log(`[Router] all subagents done in ${Date.now()-o}ms total`),a}parseJSON(e){let t=e.trim();t.startsWith("```")&&(t=t.replace(/^```(?:json)?\s*/,"").replace(/```\s*$/,"").trim());try{return JSON.parse(t)}catch(n){let r=t.match(/\{[\s\S]*\}/);if(r)return JSON.parse(r[0]);throw n}}};var V=class{name;description;domain;toolNames;systemPrompt;cortexFallbackTool;mustCallTool;llm;registry;maxToolCalls;historyMessages;debug;constructor(e){if(this.name=e.name,this.description=e.description,this.domain=e.domain,this.toolNames=new Set(e.toolNames),this.systemPrompt=e.systemPrompt,this.cortexFallbackTool=e.cortexFallbackTool,this.mustCallTool=e.mustCallTool??!1,this.cortexFallbackTool&&!this.toolNames.has(this.cortexFallbackTool))throw new Error(`Subagent "${e.name}": cortexFallbackTool "${this.cortexFallbackTool}" must be in toolNames.`);this.llm=e.llm,this.registry=e.registry,this.maxToolCalls=e.options?.maxToolCalls??5,this.historyMessages=e.options?.historyMessages??10,this.debug=e.options?.debug??!1}getCard(){return{name:this.name,description:this.description,domain:this.domain}}getTools(){return this.registry.getDefinitions().filter(e=>this.toolNames.has(e.name))}async run(e,t,n){let r=this.getTools();if(this.debug&&(console.log(`
|
|
261
|
+
[${this.name}] query: "${e.query}"`),console.log(` [${this.name}] tools available: ${r.map(T=>T.name).join(", ")}`)),r.length===0)return{subagentName:this.name,steps:[],finalAnswer:`Subagent "${this.name}" has no registered tools available.`,toolResults:[],toolMessages:[]};let o=[],s=[],a=[],i=new Set,c=0,u=!1,d=[];n?.walletAddress&&d.push(`Connected wallet: ${n.walletAddress}`),n?.chain&&d.push(`Current chain: ${n.chain}`);let p=d.length>0?`User context:
|
|
262
|
+
${d.join(`
|
|
263
|
+
`)}`:"",m=e.language?`The user's current language is "${e.language}" (BCP-47). Write every user-facing string you produce \u2014 replies AND any localized tool arguments \u2014 in this language.`:"",h=[p,m,`Routed query: ${e.query}`,e.reasoning?`Router reasoning: ${e.reasoning}`:"","Use only the tools you own. If you already have enough information, respond directly in the user's language."].filter(Boolean).join(`
|
|
264
|
+
|
|
265
|
+
`),f=Ro(t,this.toolNames),g=ye(f,this.historyMessages),y=[{role:"system",content:this.systemPrompt,timestamp:0},...g,{role:"user",content:h,timestamp:Date.now()}],k=6e3,w=this.maxToolCalls+2;for(let T=0;T<w;T++){let P=c<this.maxToolCalls,x=P?r:void 0,A=await this.llm.chat(y,x);if(A.toolCalls.length===0){if(c===0&&this.cortexFallbackTool&&P){this.debug&&console.log(` [${this.name}] no tool called \u2014 forcing fallback to ${this.cortexFallbackTool}`),y.push({role:"user",content:`You answered without calling any tool. This is not allowed for on-chain queries. Call "${this.cortexFallbackTool}" now with a clear, well-formed English prompt that captures the user's intent. Resolve ambiguity with sensible defaults (chain: Ethereum unless specified; result count: top 10; time window: last 24h; sort key: 24h volume for "top/best/trending" queries). Do NOT answer in text \u2014 call the tool.`,timestamp:Date.now()});continue}if(c===0&&this.mustCallTool&&P&&!u){u=!0,this.debug&&console.log(` [${this.name}] no tool called \u2014 forcing the LLM to pick a tool`),y.push({role:"user",content:"You answered in text without calling any tool. That is not allowed here: this request needs you to open a form. Call exactly ONE of your tools that matches the user's intent now. Pass only the fields the user actually gave; leave everything else blank \u2014 the tool/form collects the rest (including a token/amount picker when needed). Do NOT ask the user for missing fields. Do NOT answer in text \u2014 call the tool.",timestamp:Date.now()});continue}return this.debug&&console.log(` [${this.name}] done \u2014 ${c} tool call(s), answer: "${A.text.slice(0,80)}\u2026"`),{subagentName:this.name,steps:o,finalAnswer:A.text,toolResults:s,toolMessages:a}}let I={role:"assistant",content:A.text||"",toolCalls:A.toolCalls,timestamp:Date.now()};y.push(I);let S=[];for(let _ of A.toolCalls){if(!this.toolNames.has(_.toolName)){let G=`Tool "${_.toolName}" is not owned by ${this.name}`;this.debug&&console.log(` [${this.name}] WARN: ${G}`),o.push({phase:"observe",content:G,timestamp:Date.now()}),y.push({role:"tool",content:G,toolName:_.toolName,toolCallId:_.callId,timestamp:Date.now()});continue}let C=`${_.toolName}::${JSON.stringify(_.args)}`;if(i.has(C)){let G=`Skipped duplicate call: ${_.toolName}`;this.debug&&console.log(` [${this.name}] SKIP duplicate: ${_.toolName}`),o.push({phase:"observe",content:G,timestamp:Date.now()}),y.push({role:"tool",content:G,toolName:_.toolName,toolCallId:_.callId,timestamp:Date.now()});continue}i.add(C),c++,this.debug&&console.log(` [${this.name}] call #${c}: ${_.toolName} ${JSON.stringify(_.args)}`),o.push({phase:"think",content:A.text||`Calling ${_.toolName}`,timestamp:Date.now()}),o.push({phase:"act",content:`Calling tool: ${_.toolName}`,toolCall:{toolName:_.toolName,args:_.args},timestamp:Date.now()});let q=Date.now(),K=await this.registry.execute(_.toolName,_.args,n);s.push(K);let H=K.success?JSON.stringify(K.data):`Error: ${K.error}`;if(this.debug){let G=K.success?"OK":"ERR",be=K.success?`${H.slice(0,120)}${H.length>120?"\u2026":""}`:K.error;console.log(` [${this.name}] ${G} (${Date.now()-q}ms): ${be}`)}o.push({phase:"observe",content:H,toolResult:K,timestamp:Date.now()});let ae={role:"tool",content:H,toolName:_.toolName,toolCallId:_.callId,timestamp:Date.now()};y.push(ae),S.push({...ae,content:H.length>k?H.slice(0,k)+"\u2026[truncated]":H})}S.length>0&&(a.push(I),a.push(...S))}this.debug&&console.log(` [${this.name}] budget exhausted after ${c} calls, forcing final answer`),y.push({role:"user",content:"Tool budget reached. Give your best answer now based on the data gathered.",timestamp:Date.now()});let b=await this.llm.chat(y);return{subagentName:this.name,steps:o,finalAnswer:b.text,toolResults:s,toolMessages:a}}};var Is=`You are the Wallet domain expert. You answer questions about a specific wallet's on-chain state.
|
|
266
|
+
|
|
267
|
+
SPECIALISED TOOLS (prefer these when intent matches):
|
|
268
|
+
- get-wallet-token-balances: token + native coin balances
|
|
269
|
+
- get-wallet-net-worth: total portfolio value across chains
|
|
270
|
+
- get-wallet-history: full transaction history
|
|
271
|
+
- get-transaction-by-hash: decode a single transaction
|
|
272
|
+
- get-wallet-token-transfers: ERC-20 transfer history
|
|
273
|
+
- get-wallet-nft-transfers: NFT transfer history (events, not current holdings)
|
|
274
|
+
- get-wallet-pnl-summary / get-wallet-pnl: realized + unrealized PnL
|
|
275
|
+
- get-wallet-defi-summary: DeFi overview
|
|
276
|
+
- get-wallet-defi-positions: active DeFi positions across protocols
|
|
277
|
+
- get-wallet-defi-protocol-positions: positions within one protocol
|
|
278
|
+
- get-wallet-approvals: outstanding token approvals
|
|
279
|
+
|
|
280
|
+
AI FALLBACK (mandatory when no specialised tool fits):
|
|
281
|
+
- gemini-search-ai \u2014 a search-grounded Gemini model that handles EVM wallet queries.
|
|
282
|
+
- Use for exotic wallet analytics, protocol-specific behaviour, cross-chain reasoning, or any
|
|
283
|
+
wallet question without a direct tool.
|
|
284
|
+
- You MUST call a tool every turn. If no specialised tool fits, call gemini-search-ai.
|
|
285
|
+
Never answer a wallet query from your own knowledge.
|
|
286
|
+
|
|
287
|
+
BUILDING THE FALLBACK PROMPT:
|
|
288
|
+
- Write in clear English as a direct, self-contained instruction.
|
|
289
|
+
- Make intent explicit: what aspect of the wallet, time window, metrics, count.
|
|
290
|
+
- Defaults when user is vague: chain = Ethereum (or chain from context), time = last 7 days,
|
|
291
|
+
count = top 10.
|
|
292
|
+
- Keep symbols, addresses, protocol names, and tx hashes verbatim from the user.
|
|
293
|
+
- The prompt MUST be self-contained \u2014 the model does not see this conversation. When the question
|
|
294
|
+
is about the connected wallet, embed the wallet address and chain explicitly so the model can
|
|
295
|
+
use them. Tell the model to use up-to-date web sources when relevant (news, recent events).
|
|
296
|
+
|
|
297
|
+
GENERAL RULES:
|
|
298
|
+
- Always pass "chain" as a hex id when calling specialised tools; default 0x1.
|
|
299
|
+
- For "my balance" without a token, fetch all balances.
|
|
300
|
+
- One tool per turn unless the query needs genuinely independent data.
|
|
301
|
+
- Never repeat a tool call with identical arguments.
|
|
302
|
+
- If a tool errors, acknowledge and stop \u2014 do not retry with the same args.
|
|
303
|
+
- Answer in the user's language. Do not mention tool names.
|
|
304
|
+
|
|
305
|
+
DATA INTEGRITY:
|
|
306
|
+
- Never invent or guess transaction hashes, addresses, or amounts.
|
|
307
|
+
- All on-chain values must come verbatim from actual tool results in this conversation.
|
|
308
|
+
- For follow-ups like "this tx" / "that one", look up the exact hash from the most
|
|
309
|
+
recent get-wallet-history or get-wallet-token-transfers result. Never fabricate.
|
|
310
|
+
|
|
311
|
+
PAGINATION:
|
|
312
|
+
- History responses include "cursor" + "hasMore". For "more" / "next page" / "ti\u1EBFp theo",
|
|
313
|
+
pass the cursor from the LAST history response in conversation. Always tell the user
|
|
314
|
+
how many results were returned and whether more pages exist.
|
|
315
|
+
|
|
316
|
+
Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea .`,yn=["get-wallet-token-balances","get-wallet-history","get-wallet-token-transfers","get-wallet-nft-transfers","get-wallet-net-worth","get-wallet-pnl-summary","get-wallet-pnl","get-wallet-approvals","get-wallet-defi-summary","get-wallet-defi-positions","get-wallet-defi-protocol-positions","get-transaction-by-hash","gemini-search-ai"];function jt(l,e,t){return new V({name:"wallet-agent",domain:"wallet",description:["Wallet-scoped on-chain state. Specialised tools cover balances, net worth, transaction history,","single-tx lookup, ERC-20 / NFT transfers, PnL, DeFi positions, and approvals.","For wallet queries without a direct tool , this agent falls","back to a search-grounded Gemini model internally \u2014 it never refuses a wallet-scoped query","and always calls a tool.","NOT in scope: market-wide token data (\u2192 token-agent), NFT current holdings (\u2192 nft-agent)."].join(" "),toolNames:[...yn],systemPrompt:Is,cortexFallbackTool:"gemini-search-ai",llm:l,registry:e,options:t})}var Ds=`You are the Token domain expert. You answer market-wide token questions, not wallet-specific ones.
|
|
317
|
+
|
|
318
|
+
SPECIALISED TOOLS (prefer these when intent matches):
|
|
319
|
+
- get-token-info: metadata (name, symbol, decimals, address, logo)
|
|
320
|
+
- get-token-holders: holder distribution and concentration
|
|
321
|
+
- get-token-analytics: volume, liquidity, buy/sell activity
|
|
322
|
+
- get-token-score: risk / trust score
|
|
323
|
+
- get-trending-tokens: generic momentum list (no category filter)
|
|
324
|
+
- get-top-gainers: top price-gainer ranking over a chosen window (no category filter)
|
|
325
|
+
|
|
326
|
+
AI FALLBACK (mandatory when no specialised tool fits):
|
|
327
|
+
- gemini-search-ai \u2014 a search-grounded Gemini model that handles EVM token queries.
|
|
328
|
+
- Use it for: token category / theme queries (meme, AI, gaming, L2, RWA), protocol-specific
|
|
329
|
+
questions, cross-chain comparisons, anything without a direct tool.
|
|
330
|
+
- You MUST call a tool every turn. If no specialised tool fits, call gemini-search-ai.
|
|
331
|
+
Never answer a token query from your own knowledge.
|
|
332
|
+
|
|
333
|
+
BUILDING THE FALLBACK PROMPT:
|
|
334
|
+
- Write in clear English, as a direct, self-contained instruction (not "the user wants\u2026").
|
|
335
|
+
- Make intent explicit: name the category, sort key, chain (Ethereum / BSC / Base / \u2026),
|
|
336
|
+
result count, fields.
|
|
337
|
+
- Defaults when user is vague: chain = Ethereum (or chain from user context), count = top 10,
|
|
338
|
+
time = last 24h, sort = 24h volume for "top / best / trending".
|
|
339
|
+
- Keep symbols, addresses, and protocol names verbatim from the user.
|
|
340
|
+
- Embed chain + relevant addresses directly in the prompt \u2014 the model does not see this
|
|
341
|
+
conversation. Tell the model to use up-to-date web sources when relevant.
|
|
342
|
+
|
|
343
|
+
Example: user "show top meme tokens" \u2192 fallback prompt:
|
|
344
|
+
"List the top 10 meme-category tokens on Ethereum right now, ranked by 24h trading volume,
|
|
345
|
+
using up-to-date market data.
|
|
346
|
+
|
|
347
|
+
OUT OF SCOPE (do not handle):
|
|
348
|
+
- "my balance" / "my transfers" \u2192 wallet-agent
|
|
349
|
+
- "my NFTs" \u2192 nft-agent
|
|
350
|
+
|
|
351
|
+
GENERAL RULES:
|
|
352
|
+
- Always pass "chain" as a hex id when calling specialised tools; default 0x1.
|
|
353
|
+
- One tool per turn unless the query needs genuinely independent data.
|
|
354
|
+
- Never repeat a tool call with identical arguments.
|
|
355
|
+
- Answer in the user's language. Do not mention tool names.
|
|
356
|
+
|
|
357
|
+
Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea. `,bn=["get-token-info","get-token-holders","get-token-analytics","get-token-score","get-trending-tokens","get-top-gainers","gemini-search-ai"];function Yt(l,e,t){return new V({name:"token-agent",domain:"token",description:["Market-wide token data. Specialised tools cover metadata, holders, analytics, risk score,","trending, and top gainers. For token category/theme queries (meme, AI, gaming, L2, RWA),","any token question without a direct tool, this agent falls back to a","search-grounded Gemini model internally \u2014 it never refuses a token query and always calls a tool.","NOT in scope: wallet balances or transfer history (\u2192 wallet-agent), NFT data (\u2192 nft-agent)."].join(" "),toolNames:[...bn],systemPrompt:Ds,cortexFallbackTool:"gemini-search-ai",llm:l,registry:e,options:t})}var Ls=`You are the NFT domain expert. You answer questions about NFT holdings and metadata, not transfer history.
|
|
358
|
+
|
|
359
|
+
SPECIALISED TOOLS (prefer these when intent matches):
|
|
360
|
+
- get-wallet-nfts: user wants to view / browse their NFTs / holdings \u2014 returns a fixed website notice
|
|
361
|
+
- get-nft-metadata: user asks about a specific NFT (details, traits, floor) \u2014 returns a fixed website notice
|
|
362
|
+
- get-nft-contract-info: user asks about a collection / contract \u2014 returns a fixed website notice
|
|
363
|
+
- send-nft: user wants to SEND / transfer an NFT ("send nft", "transfer nft", "g\u1EEDi nft") \u2014 returns a fixed website notice
|
|
364
|
+
|
|
365
|
+
NFT NOTICE TOOLS (all of the above):
|
|
366
|
+
- They return a fixed message you MUST output verbatim (the tool result's _instructions explain). Do NOT translate, rephrase, or add to it; keep any Markdown link exactly as-is.
|
|
367
|
+
- They take no arguments \u2014 call the matching one as soon as the intent is clear; never ask the user for an address, token id, or chain.
|
|
368
|
+
|
|
369
|
+
AI FALLBACK (mandatory when no specialised tool fits):
|
|
370
|
+
- gemini-search-ai \u2014 a search-grounded Gemini model that handles EVM NFT queries.
|
|
371
|
+
- Use for: NFT category / theme queries (top PFP, trending mints, gaming NFTs), market-wide NFT
|
|
372
|
+
analytics, anything without a direct tool.
|
|
373
|
+
- You MUST call a tool every turn. If no specialised tool fits, call gemini-search-ai.
|
|
374
|
+
Never answer an NFT query from your own knowledge.
|
|
375
|
+
|
|
376
|
+
BUILDING THE FALLBACK PROMPT:
|
|
377
|
+
- Write in clear English as a direct, self-contained instruction.
|
|
378
|
+
- Make intent explicit: category, sort key, chain (name it: Ethereum / Base / \u2026), count, fields.
|
|
379
|
+
- Defaults when user is vague: chain = Ethereum (or chain from context), count = top 10,
|
|
380
|
+
time = last 24h, sort = 24h volume.
|
|
381
|
+
- Keep collection names, contract addresses, and token ids verbatim from the user.
|
|
382
|
+
- Embed the connected wallet address only when relevant to the question \u2014 the prompt MUST be
|
|
383
|
+
self-contained (the model does not see this conversation).
|
|
384
|
+
- Tell the model to use up-to-date web sources when relevant.
|
|
385
|
+
|
|
386
|
+
Example: user "top PFP collections" \u2192 fallback prompt:
|
|
387
|
+
"List the top 10 PFP-style NFT collections on Ethereum right now, ranked by 24h trading volume,
|
|
388
|
+
using up-to-date market data. For each: collection name, contract address, floor price in ETH,
|
|
389
|
+
24h volume, and item count."
|
|
390
|
+
|
|
391
|
+
SCOPE NOTES:
|
|
392
|
+
- "What NFTs do I own?" \u2192 handle here.
|
|
393
|
+
- "Send / transfer an NFT" (the action) \u2192 handle here (send-nft).
|
|
394
|
+
- "Did I send / receive an NFT?" (past events) \u2192 not here, that is transfer history (wallet-agent).
|
|
395
|
+
|
|
396
|
+
GENERAL RULES:
|
|
397
|
+
- Always pass "chain" as a hex id when calling specialised tools; default 0x1.
|
|
398
|
+
- For a specific NFT by id, use get-nft-metadata with contract address + token id.
|
|
399
|
+
- One tool per turn. Never repeat a tool call with identical arguments.
|
|
400
|
+
- Answer in the user's language. Do not mention tool names.
|
|
401
|
+
|
|
402
|
+
Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea.`,wn=["get-wallet-nfts","get-nft-metadata","get-nft-contract-info","send-nft","gemini-search-ai"];function zt(l,e,t){return new V({name:"nft-agent",domain:"nft",description:["NFT holdings, metadata, and sending. Specialised tools cover NFT holdings, single-NFT metadata,","collection / contract info, and sending/transferring an NFT. For NFT category, theme, or market-wide","questions without a direct tool, this agent falls back to a search-grounded Gemini model internally \u2014","it never refuses an NFT query and always calls a tool.",'Handles the SEND / TRANSFER-an-NFT action ("send nft", "g\u1EEDi nft"); but past NFT transfer history (events) \u2192 wallet-agent.',"Key distinction: nft-agent = NFT holdings / details / send; wallet-agent = NFT TRANSFER-EVENT history."].join(" "),toolNames:[...wn],systemPrompt:Ls,cortexFallbackTool:"gemini-search-ai",llm:l,registry:e,options:t})}var Ms=`You are the Crypto AI fallback expert powered by a search-grounded Gemini model.
|
|
403
|
+
|
|
404
|
+
You answer ANY blockchain / crypto / token / NFT / DeFi / on-chain question that does not have a
|
|
405
|
+
direct structured tool in the other agents. The underlying model has live web-search grounding \u2014
|
|
406
|
+
trust it to handle market-wide, category-level, and protocol-specific questions.
|
|
407
|
+
|
|
408
|
+
ROUTING:
|
|
409
|
+
- Call exactly ONE tool per turn: gemini-search-ai.
|
|
410
|
+
- Build the prompt to be SELF-CONTAINED \u2014 embed everything the model needs without seeing this
|
|
411
|
+
conversation: the chain (Ethereum / BSC / Base / \u2026), wallet or contract addresses,
|
|
412
|
+
token symbols, time range, sort key, count, and what to compare.
|
|
413
|
+
- Defaults when the user is vague: chain = Ethereum (or chain from user context), count = top 10,
|
|
414
|
+
time = last 24h, sort = 24h trading volume for "top / best / trending" queries.
|
|
415
|
+
- Tell the model to use up-to-date web sources when relevance demands it (news, prices,
|
|
416
|
+
recent on-chain events).
|
|
417
|
+
|
|
418
|
+
RULES:
|
|
419
|
+
- ALWAYS call the tool \u2014 NEVER refuse, NEVER answer without calling it.
|
|
420
|
+
- NEVER retry with the same prompt if the call fails \u2014 return what you have.
|
|
421
|
+
- Answer in the user's language. Do NOT mention tool names.`,kn=["gemini-search-ai"];function Qt(l,e,t){return new V({name:"ai-agent",domain:"ai",description:["Catch-all crypto / blockchain expert powered by a search-grounded Gemini model (live Google","Search). USE THIS AGENT whenever the query is about blockchain, crypto, tokens, NFTs, DeFi,","wallets, on-chain data, smart contracts, or any supported EVM chain BUT no other agent","(wallet-agent, token-agent, nft-agent, pool-agent) has a direct structured tool for it.","Scope examples: (1) token categories or themes \u2014 meme tokens, AI tokens, gaming tokens, L2 tokens, RWA;",'(2) protocol-specific questions \u2014 "how does X protocol work", "what happened to Y";',"(3) cross-contract or exploratory EVM analysis spanning many contracts;","(4) any open-ended on-chain question without a direct structured tool.","Prefer the specific agents when they clearly match (wallet balances \u2192 wallet-agent,","single-token info/analytics \u2192 token-agent, NFT holdings/metadata \u2192 nft-agent, DEX pools \u2192 pool-agent).","But for anything crypto-related that falls outside those, route HERE \u2014 do not return empty."].join(" "),toolNames:[...kn],systemPrompt:Ms,cortexFallbackTool:"gemini-search-ai",llm:l,registry:e,options:t})}var Bs=`You are the Pool / DEX Liquidity domain expert.
|
|
422
|
+
|
|
423
|
+
You answer questions about decentralized exchange liquidity pools \u2014 specifically Uniswap V2/V3/V4 pools across EVM chains \u2014 AND you guide the user through providing liquidity to a pool.
|
|
424
|
+
|
|
425
|
+
CAPABILITIES (tools you own):
|
|
426
|
+
- Top pools by chain ("top pools on Ethereum", "biggest Uniswap pools on Base")
|
|
427
|
+
- Pool ranking by metric ("pools with highest 24h volume", "most active pools by tx count", "highest APR pools")
|
|
428
|
+
- Pool composition for a chain ("which pools have the most TVL on Arbitrum")
|
|
429
|
+
- Pool detail by token pair ("details for USDC/WETH 0.05% on Base", "TVL/volume/APR of the WBTC/USDC pool")
|
|
430
|
+
- Pool search by token symbol/name/address ("find WBTC pools on Optimism", "USDC/ETH pools on Arbitrum", "pools containing PEPE")
|
|
431
|
+
- Lookup by single 0x address \u2014 V2/V3/V4 pool detail OR pool list for a token, when the user is unsure which kind it is ("what's at 0x88e6...", "tell me about this address")
|
|
432
|
+
- Yield estimation for a deposit ("n\u1EBFu t\xF4i b\u1ECF 1000$ v\xE0o pool USDC/WETH 0.05% th\xEC m\u1ED7i ng\xE0y l\u1EDDi bao nhi\xEAu?", "if I put $500 into this pool how much do I earn per day?", "APR of WBTC/ETH 0.3% on Arbitrum", "daily income for 1 ETH in this pool")
|
|
433
|
+
- Open the Add-Liquidity form for a specific pool ("add liquidity USDC/WETH 0.05% Base", "I want to provide liquidity", "farm this pool", "stake LP", "deposit into this pool", "cung c\u1EA5p thanh kho\u1EA3n", "th\xEAm thanh kho\u1EA3n")
|
|
434
|
+
- Preview the add-liquidity transaction once the user fills the form (amount + min/max price)
|
|
435
|
+
- Report the on-chain result after the user broadcasts the signed transaction
|
|
436
|
+
|
|
437
|
+
OUT OF SCOPE \u2014 do NOT handle these (route them elsewhere):
|
|
438
|
+
- "my LP positions", "my liquidity", "my DeFi positions" \u2192 wallet-agent owns wallet-scoped DeFi data
|
|
439
|
+
- "what is USDC", "token price", "trending tokens" \u2192 token-agent
|
|
440
|
+
- "my NFTs", "NFT collection" \u2192 nft-agent
|
|
441
|
+
|
|
442
|
+
RULES:
|
|
443
|
+
- ALWAYS pass "chain" as a hex id; default to "0x1" (Ethereum) when not specified.
|
|
444
|
+
- Default protocolVersion to "V3" for both top-pools and search-pools unless the user explicitly asks for V2, V4, or all versions ("ALL").
|
|
445
|
+
- Default sortBy to "tvl" unless the user asks about volume, activity, or yield. Use sortBy="apr" when the user asks about yield/APR/highest-earning pools (get-top-pools supports: tvl, volume1Day, volume1Week, volume30Day, txCount, apr).
|
|
446
|
+
- Use ONE tool per turn unless the query asks for genuinely independent data (e.g. two different chains).
|
|
447
|
+
- NEVER call the same tool twice with identical arguments.
|
|
448
|
+
- Answer in the user's language. Do NOT mention tool names or API endpoints.
|
|
449
|
+
- When showing pools, pair the two token symbols (e.g. "USDC / WETH") and include fee tier as a percentage.
|
|
450
|
+
|
|
451
|
+
TOOL SELECTION (top-pools vs search-pools vs pool-detail):
|
|
452
|
+
- "get-top-pools" \u2014 use ONLY when the user asks for the chain-wide ranking with NO specific token mentioned. Triggers: "top pools on X", "biggest pools on X", "highest-volume pools", "highest-APR pools", "most active pools". The result is the network-level leaderboard. get-top-pools CANNOT filter by token.
|
|
453
|
+
- "search-pools" \u2014 use whenever the user names ONE specific token (or a free-text query) and wants to see pools related to it. Triggers: "show pools for USDC", "USDC pools on Optimism", "pools containing PEPE", "find WBTC pools on Base", "top USDC pools", "biggest WBTC pools", "highest-volume PEPE pools". Words like "top", "biggest", "best", "highest" do NOT promote this to get-top-pools \u2014 if any token symbol is named, it stays search-pools (then sort/rank the response yourself by 24h volume desc when the user asked for "top/biggest"). One-token queries ALWAYS map to search-pools, NEVER to get-top-pools.
|
|
454
|
+
- "get-pool-detail" \u2014 use when the user names a SPECIFIC POOL by token pair (and optionally fee tier): "USDC/WETH 0.05% on Base", "details of WBTC/USDC pool". Two symbols \u2192 pool detail.
|
|
455
|
+
- Quick decision: count token symbols in the user's request. 0 symbols \u2192 get-top-pools. 1 symbol \u2192 search-pools (regardless of words like "top"/"biggest"). 2 symbols \u2192 get-pool-detail. A 0x address \u2192 lookup-pool-by-address.
|
|
456
|
+
|
|
457
|
+
POOL DETAIL FLOW:
|
|
458
|
+
- "get-pool-detail" identifies the pool BY TOKEN PAIR, never by address. Pass "token0Symbol", optionally "token1Symbol", and optionally "feeTier" (bps: 100/500/3000/10000). The tool internally searches first, filters by your inputs, then fetches detail when the filter resolves to one pool.
|
|
459
|
+
- For "details of pool USDC/WETH 0.05% on Base" \u2192 call with token0Symbol="USDC", token1Symbol="WETH", feeTier=500, chain="0x2105".
|
|
460
|
+
- For incomplete input (only one symbol, or pair without fee tier), STILL call this tool with whatever you have. If the filter matches multiple pools, the tool returns { candidates: [...] } (no "pool" field). Render candidates as a numbered list (pair, fee %, 24h volume, address) and ask the user which one. When they answer, re-call this tool with the chosen candidate's exact token0Symbol + token1Symbol + feeTier.
|
|
461
|
+
- NEVER pass a pool address to "get-pool-detail" \u2014 the tool does not accept one. NEVER fabricate pool stats from a candidates response.
|
|
462
|
+
|
|
463
|
+
ADDRESS LOOKUP FLOW:
|
|
464
|
+
- When the user pastes a single 0x address WITHOUT saying whether it is a pool or a token, use "lookup-pool-by-address" with "address" + "chain". The tool resolves both directions in parallel and returns "resolvedAs": "pool" or "resolvedAs": "token".
|
|
465
|
+
- "resolvedAs": "pool" \u2192 render the pool detail (pair, fee %, TVL, 24h volume + fees, APR, reserves, tx count). For V4 pools also mention tickSpacing, isDynamicFee, and the hook address if present. The tool auto-handles V2/V3/V4 \u2014 do NOT filter by protocol version yourself.
|
|
466
|
+
- "resolvedAs": "token" \u2192 tell the user which token it is (symbol + name) and render the returned pools as a numbered list. When the user picks one, re-call "lookup-pool-by-address" with that pool's address.
|
|
467
|
+
- If the user already says "this is a pool address" or "this is a token address", you can still use this tool \u2014 it works for either case.
|
|
468
|
+
- Use "get-pool-detail" (token-pair flow) when the user describes a pool BY SYMBOLS, and "lookup-pool-by-address" when they paste a raw address.
|
|
469
|
+
|
|
470
|
+
YIELD ESTIMATION FLOW (estimate_pool_yield):
|
|
471
|
+
|
|
472
|
+
Intent recognition \u2014 the user wants a yield estimate when they say things like:
|
|
473
|
+
"n\u1EBFu t\xF4i b\u1ECF X$ v\xE0o pool A/B th\xEC m\u1ED7i ng\xE0y l\u1EDDi bao nhi\xEAu?", "if I put $X into pool A/B how much do I earn per day?",
|
|
474
|
+
"how much daily income from $X in this pool?", "APR of pool X/Y fee Z%", "bao nhi\xEAu l\u1EE3i nhu\u1EADn m\u1ED7i ng\xE0y?",
|
|
475
|
+
"daily/weekly/monthly earnings", "how much will I make if I deposit X dollars?", "l\u1EE3i nhu\u1EADn \u01B0\u1EDBc t\xEDnh", "yield estimate".
|
|
476
|
+
These are INFORMATION questions \u2014 the user has NOT asked to actually deposit yet.
|
|
477
|
+
|
|
478
|
+
CRITICAL \u2014 minimize back-and-forth. Call estimate_pool_yield IMMEDIATELY with whatever info the user gave you. The tool handles missing info on its own:
|
|
479
|
+
- ONE token mentioned (e.g. "ETH yield?", "l\u1EDDi bao nhi\xEAu khi farm USDC"): pass just token0Symbol. The tool auto-picks the highest-volume pool. Do NOT ask the user for the other token.
|
|
480
|
+
- TWO tokens, no fee tier (e.g. "USDC/WETH yield"): pass both symbols. The tool returns fee-tier candidates ONLY when multiple distinct fee tiers exist; otherwise it just returns the result. Ask the user to pick a fee tier ONLY when the tool returns candidates.
|
|
481
|
+
- No deposit amount: just call the tool. It defaults to $1000 and marks the result as a per-$1000 example. Do NOT ask the user for the deposit amount upfront.
|
|
482
|
+
- Pool already shown this turn: extract token0Symbol, token1Symbol, feeTier from the previous tool result. Do NOT re-search.
|
|
483
|
+
|
|
484
|
+
Reading the tool response:
|
|
485
|
+
- "candidates" present \u2192 multiple fee tiers, ask the user to pick (numbered list: fee % + 24h volume). Re-call with chosen feeTier and the original depositUsd (if user supplied one).
|
|
486
|
+
- "depositMode" = "default-1000-example" \u2192 the result is a per-$1000 illustration. Present it as "for every $1000 you deposit, you'd earn ~$X/day". After showing, you MAY invite the user to share their actual budget for a personalized number.
|
|
487
|
+
- "depositMode" = "user-specified" \u2192 the result is for the user's stated amount. Present directly.
|
|
488
|
+
- "autoPickedReason" present \u2192 the tool picked the top pool for them; mention this naturally ("the highest-volume USDC/WETH pool", NOT "I auto-picked").
|
|
489
|
+
|
|
490
|
+
Yield estimate presentation:
|
|
491
|
+
- Always show: pool name (pair + fee tier %), chain, APR (%), and earnings \u2014 daily / weekly / monthly / yearly \u2014 in USD.
|
|
492
|
+
- Include a one-line disclaimer about impermanent loss / data freshness.
|
|
493
|
+
- Answer in the user's language. Format USD with 2 decimals.
|
|
494
|
+
- After showing the yield, you MAY offer to open the add-liquidity form if the user seems interested in depositing.
|
|
495
|
+
|
|
496
|
+
ADD-LIQUIDITY FLOW (open-add-liquidity-form + build-add-liquidity-tx):
|
|
497
|
+
|
|
498
|
+
Intent recognition \u2014 the user wants to add liquidity whenever they say (in any language) things like:
|
|
499
|
+
"add liquidity", "provide liquidity", "add LP", "supply LP", "deposit into pool", "stake into pool", "farm this pool",
|
|
500
|
+
"th\xEAm thanh kho\u1EA3n", "cung c\u1EA5p thanh kho\u1EA3n", "b\u1ECF ti\u1EC1n v\xE0o pool", "farm pool", "v\xE0o pool", "add pool", "c\u1EA5p v\u1ED1n pool".
|
|
501
|
+
Treat all of these as the same intent. Words like "buy" / "swap" / "trade" are NOT this intent \u2014 those go to the token agent or to a swap flow.
|
|
502
|
+
|
|
503
|
+
Context carry-over (IMPORTANT):
|
|
504
|
+
When the user says "add this pool", "farm this", "I want to add liquidity to this pool" \u2014 referring to a pool already shown in the CURRENT conversation \u2014 extract token0Symbol, token1Symbol, and feeTier directly from the previous tool result (pool.token0.symbol, pool.token1.symbol, pool.feeTierBps) WITHOUT calling any search tool again. Call open-add-liquidity-form immediately with those values.
|
|
505
|
+
|
|
506
|
+
Required inputs for open-add-liquidity-form:
|
|
507
|
+
- token0Symbol, token1Symbol, feeTier (bps: 100/500/3000/10000), chain.
|
|
508
|
+
- Optional: prefillNativeAmountWei \u2014 pass when the user already named an amount (see "Amount-first inputs" below).
|
|
509
|
+
- If any required field is missing, do NOT call this tool yet. Resolve the missing piece first:
|
|
510
|
+
* Pair given but no fee tier \u2192 call get-pool-detail with just the pair so the user can pick a fee tier from the candidates.
|
|
511
|
+
* Only one symbol \u2192 ask the user for the other token (or call search-pools to suggest pools).
|
|
512
|
+
* No pool at all ("I want to farm something on Base") \u2192 call get-top-pools sortBy="apr" first, list the candidates, ask the user to pick.
|
|
513
|
+
|
|
514
|
+
Amount-first inputs (e.g. "add 0.1 ETH to a pool", "th\xEAm 0.5 ETH v\xE0o pool USDC/WETH 0.05% Base", "add $10 to this pool", "10 dollars"):
|
|
515
|
+
- The user often supplies the AMOUNT before (or together with) the pool. Remember it across turns.
|
|
516
|
+
- Native-token amount (ETH, BNB, MATIC, etc.): pass as prefillNativeAmountWei = floor(amount \xD7 10^18) as a decimal string. Example: 0.1 ETH \u2192 "100000000000000000". 0.5 ETH \u2192 "500000000000000000". Do NOT use scientific notation; emit a plain integer string.
|
|
517
|
+
- USD amount ("$10", "10 dollars", "10$", "10 USD"): pass as prefillUsdAmount = the numeric dollar value (e.g. 10, 25.5). The tool converts to native wei automatically. Do NOT compute the conversion yourself.
|
|
518
|
+
- If the user only said "add 0.1 ETH to a pool" (or "$10 to a pool") with no pool yet:
|
|
519
|
+
1. Call get-top-pools (sortBy="apr") on the relevant chain (default to userContext.chain, else ask).
|
|
520
|
+
2. Show 5\u201310 candidates. Once the user picks one, call open-add-liquidity-form with the appropriate prefill parameter.
|
|
521
|
+
|
|
522
|
+
Price-range prefill (open-add-liquidity-form) \u2014 pass these to skip the user having to type min/max manually:
|
|
523
|
+
- Explicit numbers ("range 1800 to 2200", "min 0.0005 max 0.0008", "kho\u1EA3ng gi\xE1 1800-2200"): pass minPrice + maxPrice as plain decimals in the SAME unit as pool.currentPrice (token1 per token0). When both are present, the tool ignores rangeStrategy.
|
|
524
|
+
- Strategy keywords: pass rangeStrategy when the user signals a preference but does not name numbers:
|
|
525
|
+
* "stable" \u2014 narrow \xB13 tickSpacing around current price. Triggers: "safe", "tight", "narrow", "high yield", "stablecoin pool", "t\u1EADp trung", "h\u1EB9p", "ch\u1EB7t", "an to\xE0n".
|
|
526
|
+
* "wide" \u2014 currentPrice \xD7 [0.5, 2.0]. Triggers: "wide", "broad", "set and forget", "r\u1ED9ng", "tho\u1EA3i m\xE1i", "\xEDt rebalance".
|
|
527
|
+
* "full" \u2014 full V3 tick range (V2-style). Triggers: "full range", "v2-style", "no rebalancing", "kh\xF4ng lo out of range", "to\xE0n d\u1EA3i".
|
|
528
|
+
- If the user did NOT signal anything about price range, do NOT pass rangeStrategy/minPrice/maxPrice \u2014 the FE will show preset chips and let them pick.
|
|
529
|
+
- The tool returns prefillRange in the form props; the FE pre-populates the inputs but keeps them editable.
|
|
530
|
+
|
|
531
|
+
2-turn flow overview:
|
|
532
|
+
Turn 1: open-add-liquidity-form \u2192 renders AddLiquidityForm UI (pool info + native input + price-range inputs)
|
|
533
|
+
Turn 2: preview-add-liquidity \u2192 renders ConfirmAddLiquidityTx UI (expected amounts + unsigned tx)
|
|
534
|
+
|
|
535
|
+
After open-add-liquidity-form returns (Turn 1):
|
|
536
|
+
- The FE renders the form. Briefly tell the user (in their language): pool name, chain, available balance, and any warning the tool flagged.
|
|
537
|
+
- Warning="no_balance" \u2192 tell them they must top up before they can add.
|
|
538
|
+
- Warning="insufficient_balance" \u2192 tell them the spendable balance is insufficient.
|
|
539
|
+
- Then WAIT. The user must enter a native amount AND a price range (min price + max price, both in token1-per-token0 units, i.e. the SAME unit as pool.currentPrice) before submitting.
|
|
540
|
+
- Reference values for the user: pool.currentPrice tells them where the price is right now. They should pick minPrice < currentPrice < maxPrice for a typical in-range position.
|
|
541
|
+
|
|
542
|
+
When the user submits the form (Turn 2 \u2192 preview-add-liquidity):
|
|
543
|
+
- Required: poolAddress, chain, minPrice, maxPrice, nativeAmountWei.
|
|
544
|
+
- Convert the user's native amount to wei: floor(amount \xD7 10^18) as a plain integer string. Example: 0.1 \u2192 "100000000000000000".
|
|
545
|
+
- Pass minPrice and maxPrice as plain decimal numbers in the SAME unit as pool.currentPrice. Do NOT convert to ticks \u2014 the BE handles tick conversion.
|
|
546
|
+
- The tool returns an unsigned tx (in props.unsignedTx) plus expected token0/token1 amounts. The FE renders the confirm panel; the user signs and broadcasts via their wallet.
|
|
547
|
+
|
|
548
|
+
NOTE: many chat clients submit form/confirm panels directly to the BE tool (agent.invokeTool, deterministic path), bypassing the LLM. When you DO see a conversational confirm like "OK, 0.1 ETH range 1500\u20132200" you handle it as above.
|
|
549
|
+
|
|
550
|
+
preview-add-liquidity error handling:
|
|
551
|
+
- "invalid_price_range" \u2192 minPrice/maxPrice swapped, \u22640, or equal. Ask the user for a wider range.
|
|
552
|
+
- "invalid_range_after_alignment" \u2192 range collapsed when aligned to tickSpacing. Ask for a wider range.
|
|
553
|
+
- "insufficient_balance" \u2192 user lacks native + gas reserve. Tell them the shortfall.
|
|
554
|
+
|
|
555
|
+
State awareness:
|
|
556
|
+
- If the form is already open and the user asks an info question ("what's the current price?", "what range should I pick?"), answer in TEXT \u2014 do NOT re-open the form.
|
|
557
|
+
- If the user wants to switch pools mid-flow ("no, use 0.3% instead"), call open-add-liquidity-form again with the new fee tier.
|
|
558
|
+
- If the user changes only the amount or range ("make it 0.2 ETH and tighter range 1700\u20131900"), call preview-add-liquidity again \u2014 do NOT re-open the form.
|
|
559
|
+
|
|
560
|
+
Never fabricate pool addresses, tick numbers, prices, or NFT ids. Every value must come from a tool call this turn or the immediately preceding turn.
|
|
561
|
+
|
|
562
|
+
Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea .`,Tn=["get-top-pools","get-pool-detail","search-pools","lookup-pool-by-address","open-add-liquidity-form","preview-add-liquidity","estimate_pool_yield"];function Xt(l,e,t){return new V({name:"pool-agent",domain:"pool",description:["Handles questions about decentralized exchange liquidity pools \u2014 Uniswap V2/V3/V4 across EVM chains \u2014","AND walks the user through ADDING LIQUIDITY to a pool (provide LP / farm / stake / deposit / cung c\u1EA5p thanh kho\u1EA3n).","Scope: (1) top pools by chain \u2014 biggest pools ranked by TVL, volume, or transaction count;","(2) pool composition \u2014 which token pairs dominate liquidity on a given network;","(3) pool-level analytics \u2014 fee tier, rolling volume buckets (1d/1w/30d), tx count, both tokens;","(4) pool detail by token pair (and optional fee tier) \u2014 full stats for a single pool including TVL, 24h volume, fees, APR estimate, token reserves, and weekly volume history;","(5) pool search \u2014 find pools that match a free-text query (token symbol, token name, or pool address) on a given chain;","(6) single-address lookup \u2014 when the user pastes one 0x address without saying whether it is a pool or a token, resolve both;",'(7) YIELD ESTIMATION \u2014 estimate daily/weekly/monthly/yearly fee income for a given USD deposit into a specific pool ("if I put $1000 into USDC/WETH 0.05%, how much do I earn per day?", "b\u1ECF 500$ v\xE0o pool n\xE0y l\u1EDDi bao nhi\xEAu?");',"(8) ADD LIQUIDITY \u2014 open the AddLiquidityForm UI for a chosen pool, then build the unsigned mint transaction once the user confirms amount and price range.","NOT in scope: wallet-specific LP positions or DeFi holdings the user already owns (\u2192 wallet-agent),","token metadata or token-level market data (\u2192 token-agent), NFT data (\u2192 nft-agent).",'Key distinction from token-agent: pool-agent answers "which POOLS are biggest / most active" and handles "I want to add LP",','while token-agent answers "what is this TOKEN" and "what is the market doing for this TOKEN".'].join(" "),toolNames:[...Tn],systemPrompt:Bs,llm:l,registry:e,options:t})}var Do=`You are the Uniswap V3 Subgraph DEX-pool expert.
|
|
563
|
+
|
|
564
|
+
You answer questions about Uniswap V3 liquidity pools using The Graph subgraphs. APR values are enriched from DefiLlama Yields. Concentrated-liquidity price-range data comes from the CoinPool platform.
|
|
565
|
+
|
|
566
|
+
CAPABILITIES (tools you own):
|
|
567
|
+
- Pool search by token symbol(s) ("show pool USDC", "find ETH/USDC pools", "pools containing PEPE", "stablecoin pools", "USDC pools on Base", "best WBTC pool")
|
|
568
|
+
- Trending pools / top pools when NO token is named ("trending pools", "hot pools", "most active pools", "top pools on Arbitrum")
|
|
569
|
+
- Pool detail by 0x pool address ("tell me about pool 0xabc\u2026", "details of 0x\u2026")
|
|
570
|
+
- Position lookup by Uniswap V3 NFT id ("position 962961", "pool id 12345", "tell me about position 962961")
|
|
571
|
+
- Concentrated-liquidity price-range candidates for a token pair ("what price range for ETH/USDC?", "best range for USDC/WETH", "g\u1EE3i \xFD kho\u1EA3ng gi\xE1 ETH/USDC")
|
|
572
|
+
|
|
573
|
+
OUT OF SCOPE \u2014 do NOT handle these:
|
|
574
|
+
- "my LP positions" / "my liquidity" / wallet-wide LP list \u2192 tell the user politely this subagent does not handle wallet-scoped LP data.
|
|
575
|
+
- "send / transfer my LP position" \u2192 not handled.
|
|
576
|
+
- "what is USDC" / token price / trending tokens \u2192 token-agent.
|
|
577
|
+
- "my NFTs" \u2192 nft-agent.
|
|
578
|
+
- Building add-liquidity / mint transactions \u2192 not handled. (If an add-liquidity link is configured, the ADD-LIQUIDITY LINK rule below still applies \u2014 you may answer and append that link without building any tx.)
|
|
579
|
+
|
|
580
|
+
RULES:
|
|
581
|
+
- ALWAYS pass chain values as hex ids: 0x1 Ethereum, 0xa Optimism, 0x38 BSC, 0x89 Polygon, 0x2105 Base, 0xa4b1 Arbitrum, 0xa86a Avalanche. NEVER pass chain names ("ethereum", "base", \u2026).
|
|
582
|
+
- When the user does NOT name a chain, pass \`chains: []\` (or omit \`chain\`) \u2014 the tool falls back to the connected chain. NEVER ask the user "which chain?".
|
|
583
|
+
- Only fill \`chains\` when the user explicitly named one or more networks ("on Base" \u2192 ["0x2105"], "tr\xEAn Arbitrum" \u2192 ["0xa4b1"]).
|
|
584
|
+
- If the user mentions an unsupported chain (solana, fantom, cronos, etc.), pass it verbatim so the tool returns unsupportedChains; then quote that list in your answer.
|
|
585
|
+
- Use ONE tool per turn unless the query needs genuinely independent data.
|
|
586
|
+
- NEVER call the same tool twice with identical arguments.
|
|
587
|
+
- Answer in the user's language. Do NOT mention tool names or API endpoints.
|
|
588
|
+
- Prefix data answers with "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep the word "Subgraph" as-is).
|
|
589
|
+
- When the response includes a specific pool or position, render the provided \`uniswapUrl\` / \`uniswapPositionUrl\` as a clickable markdown link. Never invent URLs.
|
|
590
|
+
|
|
591
|
+
TOOL SELECTION (count token symbols in the user's request):
|
|
592
|
+
- 0 token symbols \u2192 subgraph-trending-pools ("trending pools", "top pools on Base").
|
|
593
|
+
- 1 token symbol \u2192 subgraph-search-pools with tokens=[symbol] ("show pool USDC", "find WBTC pools", "best USDC pool", "stablecoin pools" \u2192 tokens=["USDC","USDT"]). Words like "top"/"biggest"/"best" do NOT change this \u2014 keep it search-pools and set sortBy accordingly.
|
|
594
|
+
- 2 token symbols \u2192 subgraph-search-pools with tokens=[sym0, sym1] ("ETH/USDC pools", "WBTC/ETH on Arbitrum").
|
|
595
|
+
- 0x-prefixed pool address \u2192 subgraph-pool-by-address.
|
|
596
|
+
- Numeric position id ("position 962961", "pool id 12345") \u2192 subgraph-position-detail.
|
|
597
|
+
- "Which pool is position X in?" (no need for fee history) \u2192 subgraph-pool-by-position-id.
|
|
598
|
+
- Token pair AND user explicitly asks about PRICE RANGES ("best range for ETH/USDC", "kho\u1EA3ng gi\xE1") \u2192 subgraph-coinpool-pairs.
|
|
599
|
+
|
|
600
|
+
SORT (subgraph-search-pools and subgraph-trending-pools):
|
|
601
|
+
- "best" / "top" / "biggest" with no other qualifier \u2192 sortBy="tvl".
|
|
602
|
+
- "highest APR" / "best yield" \u2192 sortBy="apr".
|
|
603
|
+
- "cheapest" / "lowest fee" \u2192 sortBy="fee".
|
|
604
|
+
- "most liquid" / "deepest" \u2192 sortBy="liquidity".
|
|
605
|
+
- "highest volume" / "most active" \u2192 sortBy="volume".
|
|
606
|
+
|
|
607
|
+
TOKEN vs CHAIN DISAMBIGUATION (CRITICAL):
|
|
608
|
+
Short aliases can be EITHER a chain or a token: OP, ARB, ETH, BNB, MATIC.
|
|
609
|
+
Rule: only treat the alias as a CHAIN when prepositions ("on", "in", "from", "across", "tr\xEAn", "t\u1EA1i") or the word "chain"/"network" appear next to it. Otherwise treat as a TOKEN.
|
|
610
|
+
Examples (always hex ids):
|
|
611
|
+
- "show pool OP" \u2192 tokens=["OP"], chains=[]
|
|
612
|
+
- "show pools on OP" \u2192 tokens=[], chains=["0xa"]
|
|
613
|
+
- "OP/USDC pool on ARB" \u2192 tokens=["OP","USDC"], chains=["0xa4b1"]
|
|
614
|
+
- "ETH/USDC tr\xEAn Arbitrum" \u2192 tokens=["ETH","USDC"], chains=["0xa4b1"]
|
|
615
|
+
|
|
616
|
+
COINPOOL RANGE GUIDANCE (when using subgraph-coinpool-pairs):
|
|
617
|
+
- APR from subgraph-coinpool-pairs is the APR earned ONLY within that specific price range. NEVER present it as the overall pool APR.
|
|
618
|
+
- Always pair APR with the min\u2013max range, e.g. "APR 14.3% (range: 1800 \u2013 2200 USDC/ETH)".
|
|
619
|
+
- When suggesting the best range, point to the highest-APR entry and call out that narrower ranges = higher concentration = higher returns but more risk of going out of range.
|
|
620
|
+
|
|
621
|
+
FOLLOW-UPS / CONTINUATIONS:
|
|
622
|
+
- Short replies like "yes", "tell me more", "the first one", "show ranges" refer back to the previous turn. Extract tokens/chains/pool addresses from the prior assistant message and call the appropriate tool. NEVER ask for clarification when history clearly shows what was discussed.
|
|
623
|
+
|
|
624
|
+
NEVER fabricate pool addresses, token prices, APRs, or position ids. Every value must come from a tool call this turn or the immediately preceding turn.`;function Os(l){return l?`${Do}
|
|
625
|
+
|
|
626
|
+
ADD-LIQUIDITY LINK:
|
|
627
|
+
- If the user mentions adding liquidity / adding a pool / adding a position (including phrases like "add liquidity", "add pool", "add position", "create pool", "create pair", "th\xEAm thanh kho\u1EA3n", "th\xEAm pool", "th\xEAm position"), keep your normal answer content and append this exact link at the end of the response: ${l}`:Do}var Lo=["subgraph-search-pools","subgraph-trending-pools","subgraph-pool-by-address","subgraph-pool-by-position-id","subgraph-position-detail","subgraph-coinpool-pairs"];function Wn(l,e,t){return new V({name:"pool-subgraph-agent",domain:"pool",description:["Answers questions about Uniswap V3 liquidity pools using The Graph subgraphs as the primary source","(APR is enriched from DefiLlama Yields; concentrated-liquidity price ranges come from CoinPool).","Scope: (1) pool search by token symbols on one or more EVM chains, with TVL/APR/fee filters and sort by tvl/volume/apr/fee/liquidity;","(2) trending pools ranked by 24h volume on a chain;","(3) pool detail for a specific 0x pool address;","(4) lookup the underlying pool for a Uniswap V3 NFT position id (numeric, e.g. 962961);","(5) full position detail (deposits, withdrawals, collected fees) for a numeric position id;","(6) CoinPool concentrated-liquidity price-range candidates with per-range APR for a token pair.",'NOT in scope: wallet-wide LP holdings ("my positions"), sending/transferring LP NFTs, add-liquidity action flows,',"token metadata (\u2192 token-agent), NFT data (\u2192 nft-agent).","Prefer this subagent over pool-agent when the deployment is configured to use Subgraph data (the two are mutually exclusive at config time)."].join(" "),toolNames:[...Lo],systemPrompt:Os(t?.linkAddLiquidity),llm:l,registry:e,options:t})}var $s=`Call exactly ONE tool every turn \u2014 ALWAYS, even for a bare verb with no details ("send token", "buy", "approve"). Never reply in text and never ask the user for missing fields (token, amount, recipient): the tool opens a picker/form that collects them.
|
|
628
|
+
|
|
629
|
+
Tools:
|
|
630
|
+
- open-send-native-form \u2014 native coin (ETH/BNB/MATIC/\u2026)
|
|
631
|
+
- open-send-token-form \u2014 ERC-20
|
|
632
|
+
- open-buy-token-form \u2014 buy a token (named OR, if none named, returns trending picks)
|
|
633
|
+
- open-swap-token-form \u2014 swap a token the user ALREADY HOLDS into another ("swap USDC to USDT", "swap USDC")
|
|
634
|
+
- open-approve-token-form \u2014 ERC-20 approve/revoke
|
|
635
|
+
|
|
636
|
+
Intent \u2192 tool:
|
|
637
|
+
- "send eth" / "transfer 0.1 eth" \u2192 open-send-native-form
|
|
638
|
+
- "send <token>" / "send usdc" / "transfer 50 DAI" \u2192 open-send-token-form WITH token_symbol = the named token (e.g. token_symbol="USDC"); "send token" / "transfer some token" (no token named) \u2192 open-send-token-form, leave token_symbol blank \u2192 wallet picker. ALWAYS set token_symbol whenever the user names a token.
|
|
639
|
+
- "buy <token>" \u2192 open-buy-token-form (with token_symbol); "buy a token / buy trending" (no token named) \u2192 open-buy-token-form (leave token_symbol blank)
|
|
640
|
+
- "swap <A> to <B>" / "\u0111\u1ED5i <A> sang <B>" / "convert/exchange <A> for <B>" \u2192 open-swap-token-form (from_symbol = A, to_symbol = B); "swap <A>" (only source named) \u2192 open-swap-token-form (from_symbol = A, leave to_symbol blank \u2192 trending picker); "swap" (nothing named) \u2192 open-swap-token-form (leave both blank \u2192 wallet picker). SWAP is when the user spends a token they hold; BUY is when they name a token to acquire and pick a wallet token to pay with.
|
|
641
|
+
- "approve" \u2192 open-approve-token-form
|
|
642
|
+
|
|
643
|
+
Pass only what the user gave. Leave the rest blank \u2014 the tool / form handles it. Don't invent values.
|
|
644
|
+
NFT sends are NOT handled here \u2014 they belong to the nft-agent.
|
|
645
|
+
|
|
646
|
+
Skip the tool ONLY if: walletAddress missing (ask to connect) \xB7 user explicitly names a chain different from userContext.chain (ask to switch).
|
|
647
|
+
|
|
648
|
+
After the tool returns: 1 short sentence in the user's language. Don't list missing fields. Don't mention tool names.`,vn=["open-send-native-form","open-send-token-form","open-buy-token-form","open-swap-token-form","open-approve-token-form"];function Jt(l,e,t){return new V({name:"wallet-action-agent",domain:"wallet-action",description:["User wants to PERFORM an on-chain action (send, buy, swap, approve).","This agent picks the matching open-*-form tool which emits a pre-filled form (or, for buy/swap, a confirm panel) for the FE to render.","The FE \u2014 not this agent \u2014 builds and submits the tx after the user confirms.","NOT in scope: read-only wallet/token/NFT queries, sending/transferring an NFT (\u2192 nft-agent), liquidity provisioning."].join(" "),toolNames:[...vn],systemPrompt:$s,mustCallTool:!0,llm:l,registry:e,options:t})}function Sn(l,e,t,n){let r=(c,u)=>{let d=n?.[c];return d===void 0?u:d},o=r("pool-subgraph",!1),s=n?.pool===!0,a=o?s:r("pool",!0),i=[];return r("wallet",!0)&&i.push(jt(l,e,t)),r("wallet-action",!0)&&i.push(Jt(l,e,t)),r("token",!0)&&i.push(Yt(l,e,t)),r("nft",!0)&&i.push(zt(l,e,t)),a&&i.push(Xt(l,e,t)),o&&i.push(Wn(l,e,t)),r("ai",!0)&&i.push(Qt(l,e,t)),i}var qs=`You are a response synthesizer. Given the user's question and tool execution results, produce a clear, helpful final answer.
|
|
649
|
+
|
|
650
|
+
Rules:
|
|
651
|
+
- Be concise but complete.
|
|
652
|
+
- Present data in a readable format (use lists, tables, or bullet points when appropriate).
|
|
653
|
+
- If tool data contains the answer, cite the relevant facts and numbers.
|
|
654
|
+
- If tools returned errors, explain honestly what happened.
|
|
655
|
+
- Respond in the same language as the user's message.
|
|
656
|
+
- Do NOT mention internal tool names, JSON structures, or technical implementation details.
|
|
657
|
+
`,Mt=class{llm;constructor(e){this.llm=e}async synthesise(e,t,n,r){if(!t.steps.some(m=>m.phase==="act")&&t.finalAnswer)return Mo(t.finalAnswer);let s=t.steps.filter(m=>m.phase==="observe").map((m,h)=>`[Result ${h+1}]: ${m.content}`).join(`
|
|
658
|
+
`),a=de(n),i=ye(a,8),c=(r||"").trim().toLowerCase(),u=c?`IMPORTANT: Reply in BCP-47 language "${c}" (the language the user actually typed). Tool results may contain text in other languages \u2014 ignore that and reply ONLY in "${c}".`:`IMPORTANT: The user wrote in the language of this message: "${e}". Reply in that exact language, ignoring any other languages that appear inside tool results.`,d=[{role:"system",content:qs,timestamp:0},...i,{role:"user",content:[`User question: ${e}`,"",`Agent reasoning: ${t.finalAnswer}`,"",s?`Tool results:
|
|
659
|
+
${s}`:"","",u].filter(Boolean).join(`
|
|
660
|
+
`),timestamp:Date.now()}],p=await this.llm.chat(d);return Mo(p.text)}};function Mo(l){return l&&l.replace(/^\[[a-z0-9-]+-agent\]\s*/i,"").replace(/\n\n\[[a-z0-9-]+-agent\]\s*/gi,`
|
|
661
|
+
|
|
662
|
+
`)}var Fs=`You are a CONTEXTUALIZER.
|
|
663
|
+
|
|
664
|
+
Your ONLY job: read the conversation history + the user's latest message,
|
|
665
|
+
and produce a SELF-CONTAINED version of that message.
|
|
666
|
+
|
|
667
|
+
Output STRICT JSON with this shape (no prose, no code fences):
|
|
668
|
+
|
|
669
|
+
{
|
|
670
|
+
"isFollowUp": <true|false>,
|
|
671
|
+
"rewrittenQuery": "<self-contained question in the user's language>",
|
|
672
|
+
"topicChanged": <true|false>,
|
|
673
|
+
"reasoning": "<one short line, user's language>",
|
|
674
|
+
"mentionedChain": "<blockchain network the user explicitly names this turn, e.g. 'unichain', 'polygon', 'ethereum'; empty string if none>",
|
|
675
|
+
"needsWebSearch": <true|false>
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
## DEFINITIONS
|
|
679
|
+
|
|
680
|
+
- "Self-contained" = a reader with ZERO access to prior turns can still
|
|
681
|
+
understand exactly what the user is asking.
|
|
682
|
+
- "Follow-up" = the message depends on prior turns (pronoun, ellipsis,
|
|
683
|
+
short confirmation/negation, ordinal reference, continuation cue, or
|
|
684
|
+
any elliptical reply to a question the assistant just asked).
|
|
685
|
+
|
|
686
|
+
## RULES
|
|
687
|
+
|
|
688
|
+
1. If the message is ALREADY self-contained, set isFollowUp=false and
|
|
689
|
+
copy the message verbatim into rewrittenQuery.
|
|
690
|
+
2. If it's a follow-up, REWRITE it using the most recent relevant turn.
|
|
691
|
+
- Replace pronouns and deictics with their concrete antecedent.
|
|
692
|
+
- Fold in the verb/subject that was implied.
|
|
693
|
+
- Preserve the user's language and tone.
|
|
694
|
+
- Do NOT invent facts not present in history.
|
|
695
|
+
3. If you cannot resolve the reference from history, set isFollowUp=true,
|
|
696
|
+
topicChanged=false, and copy the message verbatim \u2014 downstream code
|
|
697
|
+
will ask the user for clarification.
|
|
698
|
+
4. topicChanged=true ONLY when the user clearly pivots away from the
|
|
699
|
+
active subject (e.g. after discussing tokens, they suddenly ask about
|
|
700
|
+
a random NFT collection).
|
|
701
|
+
5. Never answer the user. Never produce tool calls. JSON ONLY.
|
|
702
|
+
6. mentionedChain: set it to the blockchain network the user explicitly names
|
|
703
|
+
this turn (resolve it from context for follow-ups). Use the network's common
|
|
704
|
+
name. Leave it "" when the user names no specific chain.
|
|
705
|
+
7. needsWebSearch: judge the REWRITTEN query. Set true ONLY when answering it
|
|
706
|
+
correctly requires LIVE or UP-TO-DATE information from the web:
|
|
707
|
+
- current/real-time prices, market cap, volume, rankings;
|
|
708
|
+
- recent news, announcements, launches, hacks, listings;
|
|
709
|
+
- anything anchored to "now", "today", "latest", "current", "this week";
|
|
710
|
+
- market analysis, trend/sentiment, comparisons of live data.
|
|
711
|
+
Set false for everything the model can answer on its own:
|
|
712
|
+
- greetings, thanks, small talk;
|
|
713
|
+
- questions about THIS conversation or what was said earlier;
|
|
714
|
+
- definitions, how-something-works, general/evergreen knowledge;
|
|
715
|
+
- any request another tool/subagent would handle (it never reaches search).
|
|
716
|
+
When unsure, prefer false.
|
|
717
|
+
|
|
718
|
+
## EXAMPLES
|
|
719
|
+
|
|
720
|
+
History: assistant listed "1. USDC 2. ETH 3. PEPE"
|
|
721
|
+
User: "detail the first"
|
|
722
|
+
\u2192 {"isFollowUp": true, "rewrittenQuery": "show details for USDC",
|
|
723
|
+
"topicChanged": false, "reasoning": "resolved 'the first' \u2192 USDC",
|
|
724
|
+
"needsWebSearch": false}
|
|
725
|
+
|
|
726
|
+
History: assistant showed transaction list page 1.
|
|
727
|
+
User: "xem n\u1EEDa c\xF2n l\u1EA1i"
|
|
728
|
+
\u2192 {"isFollowUp": true,
|
|
729
|
+
"rewrittenQuery": "xem ph\u1EA7n c\xF2n l\u1EA1i c\u1EE7a l\u1ECBch s\u1EED giao d\u1ECBch (trang ti\u1EBFp theo)",
|
|
730
|
+
"topicChanged": false,
|
|
731
|
+
"reasoning": "ti\u1EBFp t\u1EE5c ph\xE2n trang l\u1ECBch s\u1EED giao d\u1ECBch",
|
|
732
|
+
"needsWebSearch": false}
|
|
733
|
+
|
|
734
|
+
History: assistant asked "do you want to see your PnL?"
|
|
735
|
+
User: "yes"
|
|
736
|
+
\u2192 {"isFollowUp": true, "rewrittenQuery": "show my PnL",
|
|
737
|
+
"topicChanged": false, "reasoning": "affirmation of PnL question",
|
|
738
|
+
"needsWebSearch": false}
|
|
739
|
+
|
|
740
|
+
History: (none)
|
|
741
|
+
User: "xin ch\xE0o, b\u1EA1n l\xE0 ai?"
|
|
742
|
+
\u2192 {"isFollowUp": false, "rewrittenQuery": "xin ch\xE0o, b\u1EA1n l\xE0 ai?",
|
|
743
|
+
"topicChanged": false, "reasoning": "l\u1EDDi ch\xE0o, kh\xF4ng c\u1EA7n d\u1EEF li\u1EC7u web",
|
|
744
|
+
"needsWebSearch": false}
|
|
745
|
+
|
|
746
|
+
History: (none)
|
|
747
|
+
User: "what's happening in the crypto market today?"
|
|
748
|
+
\u2192 {"isFollowUp": false,
|
|
749
|
+
"rewrittenQuery": "what's happening in the crypto market today?",
|
|
750
|
+
"topicChanged": false, "reasoning": "time-sensitive market news",
|
|
751
|
+
"needsWebSearch": true}
|
|
752
|
+
|
|
753
|
+
History: (none)
|
|
754
|
+
User: "explain what an L2 rollup is"
|
|
755
|
+
\u2192 {"isFollowUp": false, "rewrittenQuery": "explain what an L2 rollup is",
|
|
756
|
+
"topicChanged": false, "reasoning": "evergreen general knowledge",
|
|
757
|
+
"needsWebSearch": false}
|
|
758
|
+
|
|
759
|
+
History: tool result contained transaction with hash "0x4296cab7553c6ead42c70918fc921f28cae58cdfbf776d2553486f812710f0f3", assistant summarised it.
|
|
760
|
+
User: "Details of this transaction?"
|
|
761
|
+
\u2192 {"isFollowUp": true,
|
|
762
|
+
"rewrittenQuery": "Get details of transaction hash 0x4296cab7553c6ead42c70918fc921f28cae58cdfbf776d2553486f812710f0f3",
|
|
763
|
+
"topicChanged": false,
|
|
764
|
+
"reasoning": "resolved 'this transaction' \u2192 exact hash from previous tool result"}
|
|
765
|
+
|
|
766
|
+
History: tool result contained wallet history page 1 with cursor "eyJhbGci\u2026", assistant showed transactions.
|
|
767
|
+
User: "show more" / "next page" / "ti\u1EBFp theo"
|
|
768
|
+
\u2192 {"isFollowUp": true,
|
|
769
|
+
"rewrittenQuery": "get next page of wallet history using cursor eyJhbGci\u2026",
|
|
770
|
+
"topicChanged": false,
|
|
771
|
+
"reasoning": "pagination follow-up \u2014 cursor extracted from last tool result"}
|
|
772
|
+
|
|
773
|
+
History: discussing wallet tokens.
|
|
774
|
+
User: "what is the floor price of Bored Apes?"
|
|
775
|
+
\u2192 {"isFollowUp": false, "rewrittenQuery": "what is the floor price of Bored Apes?",
|
|
776
|
+
"topicChanged": true, "reasoning": "already self-contained; topic pivot to NFTs"}
|
|
777
|
+
|
|
778
|
+
## IMPORTANT FOR TRANSACTION REFERENCES
|
|
779
|
+
|
|
780
|
+
When the user references "this transaction", "that tx", "the transaction above", "chi ti\u1EBFt giao d\u1ECBch n\xE0y", etc.:
|
|
781
|
+
- Scan the conversation history for tool results (role: "tool") that contain transaction data.
|
|
782
|
+
- Extract the EXACT hash value (starts with "0x", 66 chars) from the most recent such result.
|
|
783
|
+
- Include it verbatim in rewrittenQuery. NEVER invent or modify a hash.
|
|
784
|
+
- If no hash is found in history, set rewrittenQuery to the original message verbatim so the subagent knows to fetch it.`,Ws=`You detect the language of a single user message.
|
|
785
|
+
|
|
786
|
+
Output STRICT JSON, no prose, no code fences:
|
|
787
|
+
{"language": "<BCP-47 tag>"}
|
|
788
|
+
|
|
789
|
+
Allowed values: "en", "vi", "ja", "zh", "ko", "ru", "th", "ar", "fr", "es",
|
|
790
|
+
"de", "pt", "it", "id", "hi", "tr". Use "" only when the message has NO
|
|
791
|
+
decidable script signal (e.g. pure numbers, emojis, or empty).
|
|
792
|
+
|
|
793
|
+
Rules:
|
|
794
|
+
- Decide from the CURRENT message text only. Ignore any context.
|
|
795
|
+
- Latin-only short replies ("ok", "yes", "no", "sure") \u2192 "en".
|
|
796
|
+
- Vietnamese is detected by diacritics (\u0103 \xE2 \u0111 \xEA \xF4 \u01A1 \u01B0, tone marks) OR
|
|
797
|
+
common Vietnamese words ("kh\xF4ng", "c\xF3", "xem", "gi\xFAp", "l\xE0m", "ti\u1EBFp").
|
|
798
|
+
- Chinese (Han only) \u2192 "zh". Japanese (Hiragana/Katakana present) \u2192 "ja".
|
|
799
|
+
- Korean (Hangul) \u2192 "ko". Cyrillic \u2192 "ru". Thai script \u2192 "th". Arabic \u2192 "ar".
|
|
800
|
+
- Never output anything other than the JSON object above.
|
|
801
|
+
|
|
802
|
+
Examples:
|
|
803
|
+
"hello" \u2192 {"language":"en"}
|
|
804
|
+
"xin ch\xE0o" \u2192 {"language":"vi"}
|
|
805
|
+
"b\u1EA1n c\xF3 th\u1EC3 gi\xFAp m\xECnh" \u2192 {"language":"vi"}
|
|
806
|
+
"ok" \u2192 {"language":"en"}
|
|
807
|
+
"\u4F60\u597D" \u2192 {"language":"zh"}
|
|
808
|
+
"\u3053\u3093\u306B\u3061\u306F" \u2192 {"language":"ja"}
|
|
809
|
+
"\uC548\uB155\uD558\uC138\uC694" \u2192 {"language":"ko"}
|
|
810
|
+
"\u043F\u0440\u0438\u0432\u0435\u0442" \u2192 {"language":"ru"}
|
|
811
|
+
"\u0E2A\u0E27\u0E31\u0E2A\u0E14\u0E35" \u2192 {"language":"th"}
|
|
812
|
+
"123" \u2192 {"language":""}`,xn=class{llm;historyMessages;debug;constructor(e,t){this.llm=e,this.historyMessages=t?.historyMessages??8,this.debug=t?.debug??!1}async rewrite(e,t,n=[]){let r=t.some(k=>k.role==="user"||k.role==="assistant"),o=Io(t),s=de(t),a=ye(s,this.historyMessages),i=[];n.length>0&&i.push(`Domain hint: the previous assistant turn was handled by ${n.join(", ")}. A short follow-up almost certainly still belongs to that domain \u2014 preserve it in the rewrite.`),o&&i.push(o);let c=i.length>0?`
|
|
813
|
+
|
|
814
|
+
${i.join(`
|
|
815
|
+
|
|
816
|
+
`)}`:"",u=Date.now(),d=[{role:"system",content:Fs,timestamp:0},...a,{role:"user",content:`Latest user message to contextualize:
|
|
817
|
+
${e}${c}
|
|
818
|
+
|
|
819
|
+
Return JSON only.`,timestamp:Date.now()}],p=[{role:"system",content:Ws,timestamp:0},{role:"user",content:e,timestamp:Date.now()}],[m,h]=await Promise.allSettled([this.llm.chat(d),this.llm.chat(p)]),f={};if(m.status==="fulfilled")try{f=this.parseJSON(m.value.text)}catch(k){this.debug&&console.log(`[QueryRewriter] rewrite parse fail: ${k}`)}else this.debug&&console.log(`[QueryRewriter] rewrite call fail: ${m.reason}`);let g="";if(h.status==="fulfilled")try{let k=this.parseJSON(h.value.text);typeof k.language=="string"&&(g=k.language.trim().toLowerCase())}catch(k){this.debug&&console.log(`[QueryRewriter] detect parse fail: ${k}`)}else this.debug&&console.log(`[QueryRewriter] detect call fail: ${h.reason}`);let y={isFollowUp:r&&typeof f.isFollowUp=="boolean"?f.isFollowUp:!1,rewrittenQuery:typeof f.rewrittenQuery=="string"&&f.rewrittenQuery.trim()?f.rewrittenQuery.trim():e,topicChanged:r&&typeof f.topicChanged=="boolean"?f.topicChanged:!1,reasoning:typeof f.reasoning=="string"?f.reasoning:"",language:g,mentionedChain:typeof f.mentionedChain=="string"?f.mentionedChain.trim():"",needsWebSearch:f.needsWebSearch===!0};if(this.debug){let k=Date.now()-u,w=y.rewrittenQuery!==e;console.log(`
|
|
820
|
+
[QueryRewriter] (${k}ms)`),console.log(` original : "${e}"`),console.log(` rewritten : "${y.rewrittenQuery}"${w?"":" (unchanged)"}`),console.log(` followUp : ${y.isFollowUp} topicChanged: ${y.topicChanged}`),y.reasoning&&console.log(` reasoning : ${y.reasoning}`),console.log(` language : ${y.language}`),console.log(` webSearch : ${y.needsWebSearch}`)}return y}parseJSON(e){let t=e.trim();t.startsWith("```")&&(t=t.replace(/^```(?:json)?\s*/,"").replace(/```\s*$/,""));let n=t.match(/\{[\s\S]*\}/);return n&&(t=n[0]),JSON.parse(t)}};var N=require("@langchain/langgraph");var Z=l=>({reducer:(e,t)=>t,default:l}),Vs=N.Annotation.Root({userMessage:(0,N.Annotation)(),doSuggest:(0,N.Annotation)(Z(()=>!0)),overrideLang:(0,N.Annotation)(Z(()=>"")),conversationMessages:(0,N.Annotation)(Z(()=>[])),lastSubagents:(0,N.Annotation)(Z(()=>[])),messageId:(0,N.Annotation)(Z(()=>"")),rewrite:(0,N.Annotation)(Z(()=>null)),effectiveQuery:(0,N.Annotation)(Z(()=>"")),turnLanguage:(0,N.Annotation)(Z(()=>"")),kbContext:(0,N.Annotation)(Z(()=>"")),decision:(0,N.Annotation)(Z(()=>null)),assignment:(0,N.Annotation)(Z(()=>null)),assignmentIndex:(0,N.Annotation)(Z(()=>0)),subResults:(0,N.Annotation)({reducer:(l,e)=>l.concat(e),default:()=>[]}),response:(0,N.Annotation)(Z(()=>null))});function Bo(l){let e=async d=>{l.addUserMessage(d.userMessage),l.needsSummary()&&await l.compactHistory();let p=l.conversationForTurn(),m=l.lastAssistantSubagents(p);return l.debug&&m.length>0&&console.log(`[AgentCore] previous turn handled by: ${m.join(", ")}`),{conversationMessages:p,lastSubagents:m,messageId:l.generateMessageId()}},t=async d=>{let p=await l.rewrite(d.userMessage,d.conversationMessages,d.lastSubagents),m=d.overrideLang||p.language||"";return{rewrite:p,effectiveQuery:p.rewrittenQuery,turnLanguage:m}},n=async d=>{let p=d.rewrite,m=p.isFollowUp?d.effectiveQuery:d.userMessage,h=await l.searchKB(m),f=l.formatKBContext(h),g=await l.tryAnswerFromKB(d.userMessage,h,f,d.messageId);return g?{kbContext:f,response:{...g,messageId:d.messageId,rewrite:p}}:{kbContext:f}},r=async d=>({decision:await l.route(d.effectiveQuery,d.conversationMessages,l.subagentCards(),d.lastSubagents)}),o=async d=>({response:{...await l.answerDirectly(d.effectiveQuery,d.conversationMessages,d.kbContext,d.doSuggest,d.messageId,d.rewrite?.needsWebSearch??!1),messageId:d.messageId,routerDecision:d.decision,rewrite:d.rewrite}}),s=async d=>{let p=await l.runSubagent(d.assignment,d.conversationMessages,d.turnLanguage);return{subResults:[{i:d.assignmentIndex,result:p}]}},a=async d=>{let p=d.subResults.slice().sort((b,T)=>b.i-T.i).map(b=>b.result);l.persistToolMessages(p);let m=l.mergeTrace(p),h=await l.synthesise(d.userMessage,m,l.synthesiserHistory(),d.turnLanguage||void 0),f=p.map(b=>b.subagentName),g=l.collectUiActions(p,d.turnLanguage||null),y=l.collectActionButtons(p,d.turnLanguage||null),k=l.subResultsTriggerNoSuggestions(p);return{response:{...await l.finaliseAnswer({userMessage:d.userMessage,answer:h,subagents:f,uiActions:g,actionButtons:y,messageId:d.messageId},{generateSuggestions:d.doSuggest&&!k&&y.length===0}),messageId:d.messageId,trace:m,routerDecision:d.decision,subagentResults:p,rewrite:d.rewrite,...g.length>0?{uiActions:g}:{},...y.length>0?{actionButtons:y}:{}}}},i=async d=>{let p=(d.rewrite?.mentionedChain||String(l.connectedChain()??"")).trim();return{response:{...await l.answerUnsupportedChain(p,d.turnLanguage,d.messageId),messageId:d.messageId,routerDecision:d.decision,rewrite:d.rewrite}}},c=d=>d.response?N.END:"route",u=d=>{let p=d.decision?.assignments??[];if(p.length===0)return"direct";let m=p.some(f=>f.subagent!=="ai-agent"),h=(d.rewrite?.mentionedChain||l.connectedChain()||"").trim();return m&&h!==""&&!jn(h)?"unsupportedChain":p.map((f,g)=>new N.Send("subagent",{...d,assignment:f,assignmentIndex:g}))};return new N.StateGraph(Vs).addNode("setup",e).addNode("contextualize",t).addNode("kb",n).addNode("route",r).addNode("direct",o).addNode("subagent",s).addNode("synthesize",a).addNode("unsupportedChain",i).addEdge(N.START,"setup").addEdge("setup","contextualize").addEdge("contextualize","kb").addConditionalEdges("kb",c,["route",N.END]).addConditionalEdges("route",u,["direct","subagent","unsupportedChain"]).addEdge("unsupportedChain",N.END).addEdge("direct",N.END).addEdge("subagent","synthesize").addEdge("synthesize",N.END).compile()}var Oo=require("js-sha3");function Pn(){let l=Date.now().toString(16),e=Math.floor(Math.random()*65535).toString(16).padStart(4,"0");return(l+e).slice(-8)}var Hs="You are a helpful AI assistant. Answer the user accurately and concisely. Respond in the same language as the user.",An=class{llm;registry;history;summarizer;router;rewriter;subagents;synthesizer;chatGraph;systemPrompt;debug;secretKey;licenseChecked=!1;userContext={};storage;storageKey;persistHistoryEnabled;kb;kbEntries;vectorKB=null;vectorKBAutoIngested=!1;vectorKBAutoIngestPromise=null;autoIngestEnabled=!1;kbAnswerThreshold;suggestionsEnabled;historyLoaded=!1;historyLoadingPromise=null;historyReady;constructor(e){this.secretKey=e.secretKey,$n(e.rpcUrls),this.llm=new le(e.llm),this.registry=new Ee,this.history=new Nt(e.maxHistoryMessages??50),this.summarizer=new It(this.llm),this.synthesizer=new Mt(this.llm),this.systemPrompt=e.systemPrompt??Hs,this.debug=e.debug??!1,this.suggestionsEnabled=e.generateSuggestions??!0,this.storage=e.storage??null,this.storageKey=e.storageKey??"keyring-agent-history",this.persistHistoryEnabled=e.persistHistory??!1,this.kbEntries=new Dt(e.knowledgeBase),this.kbEntries.setLLM(this.llm);let t=e.vectorKB;t?.enabled?(t.provider?this.kb=t.provider:(this.vectorKB=new Se({url:t.url,token:t.token,namespace:t.namespace,minScore:t.minScore,defaultTopK:t.defaultTopK,debug:this.debug}),this.kb=this.vectorKB,this.autoIngestEnabled=t.autoIngest===!0),this.debug&&console.log(`[AgentCore] vector KB enabled (${t.provider?"custom provider":"Upstash"}`+(t.namespace?`, ns=${t.namespace}`:"")+")")):this.kb=this.kbEntries;let n=e.kbAnswerThreshold;if(typeof n=="number"?this.kbAnswerThreshold=n:t?.enabled&&!t.provider?this.kbAnswerThreshold=t.minScore??1.3:this.kbAnswerThreshold=.8,this.debug&&console.log(`[AgentCore] kbAnswerThreshold = ${this.kbAnswerThreshold}`),e.moralis!==!1){let u=typeof e.moralis=="object"?e.moralis:void 0;this.registry.register(new Fe(u)),this.registry.register(new Le(u)),this.registry.register(new We(u)),this.registry.register(new Ve(u)),this.registry.register(new He(u)),this.registry.register(new Me(u)),this.registry.register(new Ge(u)),this.registry.register(new Ke(u)),this.registry.register(new Xe(u)),this.registry.register(new Ye(u)),this.registry.register(new ze(u)),this.registry.register(new Qe(u)),this.registry.register(new je(u)),this.registry.register(new $e(u)),this.registry.register(new qe(u)),this.registry.register(new Be(u)),this.registry.register(new Oe(u))}let r=e.nftLink;this.registry.register(new Je({url:r})),this.registry.register(new Ze({url:r})),this.registry.register(new et({url:r})),this.registry.register(new tt({url:r})),this.registry.register(new nt(e.llm));let o=e.subagents?.["pool-subgraph"]===!0;if((o?e.subagents?.pool===!0:e.subagents?.pool!==!1)&&e.uniswap!==!1){let u=typeof e.uniswap=="object"?e.uniswap:void 0;this.registry.register(new ot(u)),this.registry.register(new rt(u)),this.registry.register(new st(u)),this.registry.register(new at(u)),this.registry.register(new ft(u));let d=u?{isProduction:u.isProduction}:void 0;this.registry.register(new ht({baseUrl:u?.baseUrl,pool:d,minProvideUsd:u?.minProvideUsd})),this.registry.register(new gt({pool:d}))}if(o&&e.subgraph!==!1){let u=typeof e.subgraph=="object"?e.subgraph:void 0;this.registry.register(new yt(u)),this.registry.register(new bt(u)),this.registry.register(new wt(u)),this.registry.register(new kt(u)),this.registry.register(new Tt(u)),this.registry.register(new vt(u))}let a=e.moralis===!1?void 0:typeof e.moralis=="object"?e.moralis:{};this.registry.register(new Pt(a)),this.registry.register(new At(a)),this.registry.register(new _t(a)),this.registry.register(new Ct(a)),this.registry.register(new Ut(a)),this.registry.register(new Rt(a,{debug:this.debug})),this.registry.register(new Et(a,{debug:this.debug})),this.router=new Lt(this.llm,{debug:this.debug}),this.rewriter=new xn(this.llm,{debug:this.debug});let i=typeof e.subgraph=="object"?e.subgraph:void 0,c=Sn(this.llm,this.registry,{maxToolCalls:e.maxIterations??5,debug:this.debug,linkAddLiquidity:i?.linkAddLiquidity},e.subagents);this.subagents=new Map(c.map(u=>[u.name,u])),this.chatGraph=Bo(this.createGraphHost()),this.historyReady=this.loadHistory()}registerTool(e){this.registry.register(e)}registerTools(e){for(let t of e)this.registry.register(t)}unregisterTool(e){return this.registry.unregister(e)}listTools(){return this.registry.listNames()}async invokeTool(e,t,n){if(this.assertLicense(),await this.loadHistory(),!this.registry.get(e))return{answer:`Unknown tool: "${e}".`,messageId:Pn()};this.debug&&console.log(`[AgentCore] invokeTool ${e} ${JSON.stringify(t)}`);let o=n?.userMessage??`[invoke ${e}]`;this.history.add({role:"user",content:o,timestamp:Date.now()});let s=await this.registry.execute(e,t,this.userContext),a=s.data,i=s.success?a?.summary??"Done.":`Action failed: ${s.error??"unknown error"}`,c=Pn(),u=s.success&&s.ui?[this.stampLanguage(s.ui,null)]:[],d={role:"assistant",content:i,timestamp:Date.now(),subagents:["direct-invoke"],messageId:c};return u.length>0&&(d.uiActions=u),this.history.add(d),await this.persistHistory(),{answer:i,messageId:c,...u.length>0?{uiActions:u}:{}}}registerSubagent(e){this.subagents.set(e.name,e)}unregisterSubagent(e){return this.subagents.delete(e)}listSubagents(){return Array.from(this.subagents.keys())}setKnowledgeBase(e){this.kbEntries.setEntries(e),this.vectorKBAutoIngested=!1}addKnowledgeBase(e){this.kbEntries.addEntries(e),this.vectorKBAutoIngested=!1}async ingestKnowledgeBase(){if(!this.vectorKB)return{count:0};let e=this.kbEntries.getEntries();if(e.length===0)return{count:0};let t=await this.vectorKB.upsert(e);return this.vectorKBAutoIngested=!0,t}async maybeAutoIngest(e){if(!(!e||!this.vectorKB||this.vectorKBAutoIngested)){if(this.vectorKBAutoIngestPromise)return this.vectorKBAutoIngestPromise;this.vectorKBAutoIngestPromise=(async()=>{try{let{count:t}=await this.ingestKnowledgeBase();this.debug&&t>0&&console.log(`[AgentCore] auto-ingested ${t} KB entr${t===1?"y":"ies"} to vector store`)}catch(t){this.debug&&console.warn("[AgentCore] auto-ingest failed:",t)}finally{this.vectorKBAutoIngestPromise=null}})(),await this.vectorKBAutoIngestPromise}}setUserContext(e){this.userContext={...e}}getUserContext(){return{...this.userContext}}async loadHistory(){if(!this.historyLoaded){if(this.historyLoadingPromise)return this.historyLoadingPromise;this.historyLoadingPromise=this._doLoadHistory(),await this.historyLoadingPromise}}async _doLoadHistory(){if(!this.storage){this.debug&&console.log("[AgentCore] loadHistory: no storage configured"),this.historyLoaded=!0;return}if(!this.persistHistoryEnabled){this.debug&&console.log("[AgentCore] loadHistory: persistHistory disabled \u2014 starting fresh session");try{await this.storage.removeItem(this.storageKey)}catch(e){this.debug&&console.warn("[AgentCore] failed to clear stale history:",e)}this.historyLoaded=!0;return}try{let e=await this.storage.getItem(this.storageKey);if(this.debug&&console.log(`[AgentCore] loadHistory: storage key "${this.storageKey}" ${e?`has ${e.length} chars`:"is empty"}`),e){let t=JSON.parse(e);this.history.restore(t),this.debug&&console.log(`[AgentCore] Restored ${this.history.length} messages from storage`)}}catch(e){this.debug&&console.warn("[AgentCore] Failed to load history from storage:",e)}finally{this.historyLoaded=!0}}async persistHistory(){if(this.storage&&this.persistHistoryEnabled)try{let e=JSON.stringify(this.history.serialise());await this.storage.setItem(this.storageKey,e)}catch(e){this.debug&&console.warn("[AgentCore] Failed to persist history to storage:",e)}}assertLicense(){if(this.licenseChecked)return;let e="d00d67291d3c660a5034b01c0b2afbc8d4ac5552099a04f28a92d75fdb44c188";if(e&&(!this.secretKey||(0,Oo.sha3_256)(this.secretKey)!==e))throw new Error("AgentCore: invalid or missing secretKey.");this.licenseChecked=!0}async chat(e,t){this.assertLicense();let n=t?.generateSuggestions??this.suggestionsEnabled,r=typeof t?.language=="string"&&t.language.trim()?t.language.trim():"";await this.loadHistory(),await this.maybeAutoIngest(this.autoIngestEnabled);let o=Date.now();this.debug&&(console.log(`
|
|
821
|
+
${"\u2500".repeat(60)}`),console.log(`[AgentCore] query: "${e}"`));let s=await this.chatGraph.invoke({userMessage:e,doSuggest:n,overrideLang:r});return this.debug&&(console.log(`[AgentCore] turn done in ${Date.now()-o}ms total`),console.log(`${"\u2500".repeat(60)}
|
|
822
|
+
`)),s.response??{answer:"",messageId:Pn()}}createGraphHost(){return{debug:this.debug,addUserMessage:e=>this.addUserMessage(e),needsSummary:()=>this.history.needsSummary(),compactHistory:()=>this.compactHistory(),conversationForTurn:()=>{let e=this.history.getConversation();return e.length>0&&e[e.length-1].role==="user"?e.slice(0,-1):e},lastAssistantSubagents:e=>No(e),generateMessageId:()=>Pn(),rewrite:(e,t,n)=>this.rewriter.rewrite(e,t,n),searchKB:e=>this.searchKB(e),formatKBContext:e=>this.formatKBContext(e),tryAnswerFromKB:(e,t,n,r)=>this.tryAnswerFromKB(e,t,n,r),subagentCards:()=>Array.from(this.subagents.values()).map(e=>e.getCard()),route:(e,t,n,r)=>this.router.route(e,t,n,this.userContext,r),connectedChain:()=>this.userContext.chain,answerUnsupportedChain:(e,t,n)=>this.answerUnsupportedChain(e,t,n),answerDirectly:(e,t,n,r,o,s)=>this.answerDirectly(e,t,n,r,o,s),runSubagent:async(e,t,n)=>{let r=this.subagents.get(e.subagent);if(!r)return this.debug&&console.log(`[AgentCore] WARN: subagent "${e.subagent}" not registered`),{subagentName:e.subagent,steps:[],finalAnswer:`Subagent "${e.subagent}" is not registered.`,toolResults:[],toolMessages:[]};let o=Date.now(),s=await r.run({query:e.query,reasoning:e.reasoning,language:n},t,this.userContext);if(this.debug){let a=s.toolResults.map(i=>i.toolName).join(", ")||"\u2014";console.log(`[AgentCore] ${e.subagent} done in ${Date.now()-o}ms | tools: ${a}`)}return s},persistToolMessages:e=>this.persistToolMessages(e),synthesiserHistory:()=>{let e=this.history.getConversation(),t=e.length>0&&e[e.length-1].role==="user"?e.slice(0,-1):e;return de(t)},mergeTrace:e=>this.mergeTrace(e),synthesise:(e,t,n,r)=>this.synthesizer.synthesise(e,t,n,r),collectUiActions:(e,t)=>this.collectUiActions(e,t),collectActionButtons:(e,t)=>this.collectActionButtons(e,t),subResultsTriggerNoSuggestions:e=>this.subResultsTriggerNoSuggestions(e),finaliseAnswer:(e,t)=>this.finaliseAnswer(e,t)}}subResultsTriggerNoSuggestions(e){for(let t of e)for(let n of t.toolResults){if(!n.success)continue;if(this.registry.get(n.toolName)?.noSuggestions===!0)return!0}return!1}collectActionButtons(e,t){let n=[],r=new Set;for(let o of e)for(let s of o.toolResults)if(!(!s.success||!Array.isArray(s.actionButtons))){for(let a of s.actionButtons)if(a&&typeof a=="object"&&typeof a.label=="string"&&a.label.trim()&&typeof a.prompt=="string"&&a.prompt.trim()){let i=`${a.label}\0${a.prompt}`;if(r.has(i))continue;r.add(i),n.push({label:a.label,prompt:a.prompt,...t?{language:t}:{}})}}return n}collectUiActions(e,t){let n=[];for(let r of e)for(let o of r.toolResults)o.success&&o.ui&&typeof o.ui=="object"&&typeof o.ui.component=="string"&&n.push(this.stampLanguage(o.ui,t));if(this.debug&&n.length>0){let r=n.map(o=>`${o.component}=${o.language}`).join(", ");console.log(`[AgentCore] uiActions stamped (rewrite.language="${t??""}"): ${r}`)}return n}stampLanguage(e,t){return!t||e.language?e:{...e,language:t}}addUserMessage(e){this.history.add({role:"user",content:e,timestamp:Date.now()})}async searchKB(e){let t=await this.kb.search(e);if(this.debug)if(t.length===0)console.log(`[AgentCore] KB no hits for "${e}"`);else{let n=t.slice(0,3).map(r=>`${r.score.toFixed(3)} ${this.snippet(r.entry.question,60)}`).join(" | ");console.log(`[AgentCore] KB hits (${t.length}): ${n}`)}return t}snippet(e,t){return e.length<=t?e:e.slice(0,t-1)+"\u2026"}formatKBContext(e){return e.length===0?"":e.map((t,n)=>`[Reference ${n+1}]
|
|
823
|
+
Q: ${t.entry.question}
|
|
824
|
+
A: ${t.entry.answer}`).join(`
|
|
825
|
+
|
|
826
|
+
`)}async tryAnswerFromKB(e,t,n,r){if(!n||t.length===0)return this.debug&&console.log("[AgentCore] KB skip: no hits \u2192 router pipeline"),null;let o=t[0].score;if(o<this.kbAnswerThreshold)return this.debug&&console.log(`[AgentCore] KB skip: top score ${o.toFixed(3)} < threshold ${this.kbAnswerThreshold} \u2192 router pipeline`),null;this.debug&&console.log(`[AgentCore] KB answer: top score ${o.toFixed(3)} \u2265 threshold ${this.kbAnswerThreshold} \u2192 verifying intent before KB answer`);let s=t[0].entry.question,a=[{role:"system",content:`You are a STRICT intent classifier. Respond with ONLY "yes" or "no" \u2014 no other text.
|
|
827
|
+
|
|
828
|
+
Default to "no". Only answer "yes" when you are confident.
|
|
829
|
+
|
|
830
|
+
Answer "yes" ONLY IF the user message and the FAQ question are genuinely EQUIVALENT \u2014 i.e. they ask the SAME thing and a single answer to the FAQ question would fully and directly answer the user message. Merely sharing a topic, a keyword, or a domain is NOT enough.
|
|
831
|
+
|
|
832
|
+
Answer "no" if ANY of these hold:
|
|
833
|
+
- The user is asking about a DIFFERENT aspect, sub-question, or angle of the same general topic (e.g. FAQ = "what is staking?" vs user = "how do I unstake?").
|
|
834
|
+
- The user message is broader, narrower, or only partially overlaps with the FAQ question.
|
|
835
|
+
- They merely share vocabulary or a domain but ask for different things.
|
|
836
|
+
- The user is asking for real-time data, live lookups, prices, balances, transactions, or anything that requires fetching external data \u2014 even if the FAQ question looks similar.
|
|
837
|
+
- You are uncertain.`,timestamp:0},{role:"user",content:`FAQ question: "${s}"
|
|
838
|
+
User message: "${e}"
|
|
839
|
+
|
|
840
|
+
Are these two questions EQUIVALENT \u2014 asking the same thing such that one answer fully answers both? Answer "yes" only if equivalent, otherwise "no".`,timestamp:Date.now()}],c=(await this.llm.chat(a)).text.trim().toLowerCase();if(!c.startsWith("yes"))return this.debug&&console.log(`[AgentCore] KB intent guard: verdict="${c}" \u2192 skipping KB, routing to subagent pipeline`),null;this.debug&&console.log('[AgentCore] KB intent guard: verdict="yes" \u2192 answering from KB');let u=[{role:"system",content:this.systemPrompt+`
|
|
841
|
+
|
|
842
|
+
You have reference material below. Use it ONLY if it directly addresses the user's question. Rephrase and adapt the content naturally \u2014 do NOT copy it verbatim. Do NOT supplement with general knowledge or speculate beyond what the reference provides. If the reference does not fully answer the question, say so honestly.
|
|
843
|
+
|
|
844
|
+
---
|
|
845
|
+
REFERENCE MATERIAL:
|
|
846
|
+
`+n+`
|
|
847
|
+
---`,timestamp:0},{role:"user",content:`${e}
|
|
848
|
+
|
|
849
|
+
(You MUST reply in the same language as the question above, regardless of the reference material language.)`,timestamp:Date.now()}],p=(await this.llm.chat(u)).text,m={role:"assistant",content:p,timestamp:Date.now()};return r&&(m.messageId=r),this.history.add(m),await this.persistHistory(),{answer:p,suggestedPrompts:[]}}async answerDirectly(e,t,n,r=!0,o,s=!1){this.debug&&console.log(`[AgentCore] Router returned no assignments, answering directly via LLM\u2026 (webSearch=${s})`);let a=s?"":`
|
|
850
|
+
|
|
851
|
+
You are answering WITHOUT live web access. Rely only on knowledge you are confident about. If the question needs real-time or recent information you do not have (current prices, today's news, latest events), say plainly that you do not have up-to-date data instead of guessing. Never fabricate numbers, dates, or facts.`,i=(n?this.systemPrompt+`
|
|
852
|
+
|
|
853
|
+
You may use the following reference material if relevant:
|
|
854
|
+
|
|
855
|
+
---
|
|
856
|
+
REFERENCE MATERIAL:
|
|
857
|
+
`+n+`
|
|
858
|
+
---`:this.systemPrompt)+a,c=de(t),u=[{role:"system",content:i,timestamp:0},...c,{role:"user",content:e,timestamp:Date.now()}],d=await this.llm.chat(u,void 0,{googleSearch:s});return await this.finaliseAnswer({userMessage:e,answer:d.text,messageId:o},{generateSuggestions:r})}async answerUnsupportedChain(e,t,n){this.debug&&console.log(`[AgentCore] connected chain "${e}" is not supported \u2192 short-circuit with notice`);let r=(t||"").trim(),o=[{role:"system",content:`Write ONE short, polite message telling the user that the blockchain network they want to use is NOT supported by this assistant, and inviting them to use one of the supported networks. List the supported networks: ${tn}. Do NOT mention or guess the name or chain id of the unsupported network. `+(r?`Reply in BCP-47 language "${r}".`:"Reply in the same language the user has been using.")+" Do NOT mention tools or internal implementation details.",timestamp:0},{role:"user",content:"Is my currently connected network supported?",timestamp:Date.now()}],a=(await this.llm.chat(o)).text?.trim()||`Your currently connected network is not supported yet. Supported networks: ${tn}.`;return await this.finaliseAnswer({userMessage:`[unsupported chain: ${e}]`,answer:a,messageId:n},{generateSuggestions:!1})}mergeTrace(e){let t=[];for(let r of e)t.push({phase:"think",content:`[${r.subagentName}] begin`,timestamp:Date.now()}),t.push(...r.steps),t.push({phase:"observe",content:`[${r.subagentName}] answer: ${r.finalAnswer}`,timestamp:Date.now()});let n=e.map(r=>`[${r.subagentName}] ${r.finalAnswer}`).join(`
|
|
859
|
+
|
|
860
|
+
`);return{steps:t,finalAnswer:n}}async finaliseAnswer(e,t){let{userMessage:n,answer:r,subagents:o,uiActions:s,actionButtons:a,messageId:i}=e??{},{generateSuggestions:c=!0}=t??{},u=c?await this.generateSuggestions(n,r,o??[]):[],d={role:"assistant",content:r,timestamp:Date.now()};return o&&o.length>0&&(d.subagents=o),u.length>0&&(d.suggestedPrompts=u),s&&s.length>0&&(d.uiActions=s),a&&a.length>0&&(d.actionButtons=a),i&&(d.messageId=i),this.history.add(d),await this.persistHistory(),{answer:r,suggestedPrompts:u,...a&&a.length>0?{actionButtons:a}:{}}}async generateSuggestions(e,t,n){let r=n.includes("pool")?`
|
|
861
|
+
POOL FUNNEL: This turn was handled by the pool subagent. If the answer references a SPECIFIC pool (token pair + fee tier, or a pool address), the FIRST suggestion MUST propose adding liquidity to THAT exact pool (e.g. "Add liquidity to USDC/WETH 0.05%"). A second suggestion should propose estimating yield for a deposit into the same pool. Skip this funnel only if no specific pool is identifiable in the answer.
|
|
862
|
+
`:"",o=n.length>0?`
|
|
863
|
+
Handled by subagent(s): ${n.join(", ")}`:"";try{let a=(await this.llm.chat([{role:"user",content:`Analyze this conversation and propose follow-up prompts the user is likely to want NEXT.
|
|
864
|
+
|
|
865
|
+
User: ${e}
|
|
866
|
+
Assistant: ${t}${o}
|
|
867
|
+
`+r+`
|
|
868
|
+
RULES:
|
|
869
|
+
- Derive suggestions from the user message + the assistant answer. Reference concrete entities that appear there (pool name, token symbol, chain, address, NFT, wallet).
|
|
870
|
+
- Prefer ACTION-ORIENTED suggestions over informational ones whenever the answer makes one possible. Example after showing a pool: "Add liquidity to USDC/WETH 0.05%", not "what is a liquidity pool?".
|
|
871
|
+
- Reference concrete entities from the answer (pool name, token symbol, chain, address) \u2014 never generic placeholders.
|
|
872
|
+
- If the topic is shallow, closed-ended, or fully resolved (greetings, simple yes/no, trivial facts), return an empty array: []
|
|
873
|
+
- Return a JSON array of 0 to 3 strings, no other text.
|
|
874
|
+
- Each suggestion must be concise (under 60 chars) and phrased as something the user would TYPE.
|
|
875
|
+
- Suggestions must be diverse \u2014 do not repeat the same intent in different words.
|
|
876
|
+
- Use the same language as the user.
|
|
877
|
+
- Good example (pool): ["Add liquidity to USDC/WETH 0.05%", "Estimate yield for $1000 here", "Compare with 0.3% fee tier"]
|
|
878
|
+
- Bad example: ["What is a pool?", "How do pools work?", "Tell me more"] (generic, no action, no specifics)`,timestamp:0}])).text.match(/\[[\s\S]*\]/);return a?JSON.parse(a[0]).filter(c=>typeof c=="string"&&c.length>0).slice(0,3):[]}catch{return[]}}getHistory(){return this.history.getAllFull()}getDisplayHistory(){return this.history.getAllFull().filter(e=>!(e.role!=="user"&&e.role!=="assistant"||e._intermediate||e.role==="assistant"&&!e.content?.trim()))}getLastSuggestions(){let e=this.getDisplayHistory();for(let t=e.length-1;t>=0;t--){let n=e[t];if(n.role==="assistant")return n.suggestedPrompts??[]}return[]}async clearHistory(){this.history.clear(),this.historyLoaded=!0,this.historyLoadingPromise=null,await this.persistHistory()}restoreHistory(e){this.history.restore(e)}serialiseHistory(){return this.history.serialise()}async compactHistory(){this.debug&&console.log("[AgentCore] Summarizing conversation history\u2026");let e=this.history.getAll(),t=6,n=0,r=e.length;for(let p=e.length-1;p>=0;p--)if(e[p].role==="user"&&(n++,n>=t)){r=p;break}let o=e.slice(r),s=Eo(o,o.length),a=e.length-s.length,i=de(e.slice(0,a)),c=this.history.getSummary(),u=[];c&&u.push({role:"user",content:`[Previous context summary]: ${c}`,timestamp:0}),u.push(...i);let d=await this.summarizer.summarize(u);this.history.compactWith(d,s)}persistToolMessages(e){for(let t of e)t.toolMessages.length>0&&(this.history.addMany(t.toolMessages),this.history.add({role:"assistant",content:t.finalAnswer,_intermediate:!0,timestamp:Date.now()}),this.debug&&console.log(`[AgentCore] stored ${t.toolMessages.length} tool message(s) + finalAnswer from ${t.subagentName}`))}getLLMProvider(){return this.llm}getToolRegistry(){return this.registry}};var Gs=l=>{let e=JSON.parse(l),t=Array.isArray(e)?e:e?.entries;if(!Array.isArray(t))throw new Error("JSON must be an array or { entries: [...] }");return t.map((n,r)=>{if(!n||typeof n.question!="string"||typeof n.answer!="string")throw new Error(`Entry #${r} is missing "question" or "answer"`);return n})};async function $o(l,e){let t=l.entries??(l.json?Gs(l.json):[]);return t.length===0?{count:0}:new Se(e).upsert(t)}0&&(module.exports={AI_AGENT_TOOL_NAMES,AgentCore,ApproveTokenTool,BaseNftMessageTool,BaseSwapService,BaseTool,BaseWalletActionTool,BuyTokenTool,ChatHistory,DEFAULT_PROVIDER,DEFAULT_RPC_BY_CHAIN,DebridgeAdapter,EstimatePoolYieldTool,GeminiProvider,GeminiSearchAiTool,HEX_TO_PANTOGRAPH,KnowledgeBase,MoralisService,NFTContractInfoTool,NFTMetadataTool,NFT_AGENT_TOOL_NAMES,OpenAddLiquidityFormTool,POOL_AGENT_TOOL_NAMES,PantographService,PoolByAddressTool,PoolDetailTool,PoolSearchTool,PoolService,PreviewAddLiquidityTool,RelayAdapter,Router,SWAP_PROVIDER_CONFIG,SendNativeTool,SendNftTool,SendTokenTool,Subagent,SubgraphCoinPoolPairsTool,SubgraphPoolByAddressTool,SubgraphPoolByPositionIdTool,SubgraphPoolSearchTool,SubgraphPositionDetailTool,SubgraphTrendingPoolsTool,Summarizer,SwapServiceFactory,SwapTokenTool,Synthesizer,TOKEN_AGENT_TOOL_NAMES,TRANSFER_TOPIC,TRENDING_DEFAULT_BY_HEX_CHAIN,TokenAnalyticsTool,TokenHoldersTool,TokenInfoTool,TokenScoreTool,ToolRegistry,TopGainersTool,TopPoolsTool,TransactionByHashTool,TrendingTokensTool,UNISWAP_CHAIN_SLUG,UnwrapNativeTool,UpstashKnowledgeBase,WALLET_ACTION_AGENT_TOOL_NAMES,WALLET_AGENT_TOOL_NAMES,WalletApprovalsTool,WalletDefiPositionsTool,WalletDefiProtocolPositionsTool,WalletDefiSummaryTool,WalletHistoryTool,WalletNFTsTool,WalletNetWorthTool,WalletNftTransfersTool,WalletPnlSummaryTool,WalletPnlTool,WalletTokenBalancesTool,WalletTokenTransfersTool,WrapNativeTool,ZERO_ADDRESS,buildRangePresets,buildUniswapPoolUrl,clampTick,createAiAgent,createDefaultSubagents,createNftAgent,createPoolAgent,createTokenAgent,createWalletActionAgent,createWalletAgent,ethCallAt,ethCallByChain,getAffiliateFee,getChainMeta,getDefaultRpcUrl,getGatewayAddress,getNativeTokenInfo,getPositionManagerAddress,getProviderByChain,ingestKnowledgeBase,priceToTick,resolveRpcUrl,roundTickToSpacing,rpcCall,setRpcOverrides,swapServiceFactory,tickToPrice,toPantographChain});
|