keyring-agent-core 0.1.1 → 0.2.1

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.
Files changed (301) hide show
  1. package/dist/agent/AgentCore.d.ts +37 -1
  2. package/dist/agent/AgentCore.d.ts.map +1 -1
  3. package/dist/agent/AgentCore.js +287 -144
  4. package/dist/agent/AgentCore.js.map +1 -1
  5. package/dist/agent/QueryRewriter.d.ts +17 -0
  6. package/dist/agent/QueryRewriter.d.ts.map +1 -1
  7. package/dist/agent/QueryRewriter.js +48 -4
  8. package/dist/agent/QueryRewriter.js.map +1 -1
  9. package/dist/agent/Router.d.ts.map +1 -1
  10. package/dist/agent/Router.js +73 -35
  11. package/dist/agent/Router.js.map +1 -1
  12. package/dist/agent/Subagent.d.ts +17 -0
  13. package/dist/agent/Subagent.d.ts.map +1 -1
  14. package/dist/agent/Subagent.js +32 -0
  15. package/dist/agent/Subagent.js.map +1 -1
  16. package/dist/agent/chatGraph.d.ts +364 -0
  17. package/dist/agent/chatGraph.d.ts.map +1 -0
  18. package/dist/agent/chatGraph.js +184 -0
  19. package/dist/agent/chatGraph.js.map +1 -0
  20. package/dist/agent/subagents/AiAgent.d.ts +1 -1
  21. package/dist/agent/subagents/AiAgent.d.ts.map +1 -1
  22. package/dist/agent/subagents/AiAgent.js +24 -20
  23. package/dist/agent/subagents/AiAgent.js.map +1 -1
  24. package/dist/agent/subagents/NftAgent.d.ts +1 -1
  25. package/dist/agent/subagents/NftAgent.d.ts.map +1 -1
  26. package/dist/agent/subagents/NftAgent.js +36 -26
  27. package/dist/agent/subagents/NftAgent.js.map +1 -1
  28. package/dist/agent/subagents/PoolSubgraphAgent.d.ts +6 -0
  29. package/dist/agent/subagents/PoolSubgraphAgent.d.ts.map +1 -0
  30. package/dist/agent/subagents/PoolSubgraphAgent.js +117 -0
  31. package/dist/agent/subagents/PoolSubgraphAgent.js.map +1 -0
  32. package/dist/agent/subagents/TokenAgent.d.ts +1 -1
  33. package/dist/agent/subagents/TokenAgent.d.ts.map +1 -1
  34. package/dist/agent/subagents/TokenAgent.js +23 -22
  35. package/dist/agent/subagents/TokenAgent.js.map +1 -1
  36. package/dist/agent/subagents/WalletActionAgent.d.ts +6 -0
  37. package/dist/agent/subagents/WalletActionAgent.d.ts.map +1 -0
  38. package/dist/agent/subagents/WalletActionAgent.js +55 -0
  39. package/dist/agent/subagents/WalletActionAgent.js.map +1 -0
  40. package/dist/agent/subagents/WalletAgent.d.ts +1 -1
  41. package/dist/agent/subagents/WalletAgent.d.ts.map +1 -1
  42. package/dist/agent/subagents/WalletAgent.js +18 -17
  43. package/dist/agent/subagents/WalletAgent.js.map +1 -1
  44. package/dist/agent/subagents/index.d.ts +23 -3
  45. package/dist/agent/subagents/index.d.ts.map +1 -1
  46. package/dist/agent/subagents/index.js +42 -11
  47. package/dist/agent/subagents/index.js.map +1 -1
  48. package/dist/constants/abi.d.ts +31 -0
  49. package/dist/constants/abi.d.ts.map +1 -1
  50. package/dist/constants/abi.js +24 -1
  51. package/dist/constants/abi.js.map +1 -1
  52. package/dist/functions/web3.d.ts +0 -1
  53. package/dist/functions/web3.d.ts.map +1 -1
  54. package/dist/functions/web3.js +0 -45
  55. package/dist/functions/web3.js.map +1 -1
  56. package/dist/index.d.ts +4 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +7 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/llm/GeminiProvider.d.ts.map +1 -1
  61. package/dist/llm/GeminiProvider.js +115 -34
  62. package/dist/llm/GeminiProvider.js.map +1 -1
  63. package/dist/memory/UpstashKnowledgeBase.d.ts +1 -1
  64. package/dist/memory/UpstashKnowledgeBase.js +1 -1
  65. package/dist/services/MoralisService.d.ts +3 -25
  66. package/dist/services/MoralisService.d.ts.map +1 -1
  67. package/dist/services/MoralisService.js +7 -132
  68. package/dist/services/MoralisService.js.map +1 -1
  69. package/dist/services/PantographService.d.ts +61 -1
  70. package/dist/services/PantographService.d.ts.map +1 -1
  71. package/dist/services/PantographService.js +264 -12
  72. package/dist/services/PantographService.js.map +1 -1
  73. package/dist/services/PoolService.d.ts +1 -4
  74. package/dist/services/PoolService.d.ts.map +1 -1
  75. package/dist/services/PoolService.js +18 -40
  76. package/dist/services/PoolService.js.map +1 -1
  77. package/dist/services/UniswapSubgraphService.d.ts +144 -0
  78. package/dist/services/UniswapSubgraphService.d.ts.map +1 -0
  79. package/dist/services/UniswapSubgraphService.js +606 -0
  80. package/dist/services/UniswapSubgraphService.js.map +1 -0
  81. package/dist/services/rpc.d.ts +35 -0
  82. package/dist/services/rpc.d.ts.map +1 -0
  83. package/dist/services/rpc.js +110 -0
  84. package/dist/services/rpc.js.map +1 -0
  85. package/dist/services/swap/DebridgeAdapter.d.ts +17 -0
  86. package/dist/services/swap/DebridgeAdapter.d.ts.map +1 -1
  87. package/dist/services/swap/DebridgeAdapter.js +74 -6
  88. package/dist/services/swap/DebridgeAdapter.js.map +1 -1
  89. package/dist/services/swap/SwapServiceFactory.d.ts +1 -0
  90. package/dist/services/swap/SwapServiceFactory.d.ts.map +1 -1
  91. package/dist/services/swap/SwapServiceFactory.js +8 -3
  92. package/dist/services/swap/SwapServiceFactory.js.map +1 -1
  93. package/dist/tools/builtin/ai/GeminiSearchAiTool.d.ts +36 -0
  94. package/dist/tools/builtin/ai/GeminiSearchAiTool.d.ts.map +1 -0
  95. package/dist/tools/builtin/ai/GeminiSearchAiTool.js +91 -0
  96. package/dist/tools/builtin/ai/GeminiSearchAiTool.js.map +1 -0
  97. package/dist/tools/builtin/ai/index.d.ts +2 -4
  98. package/dist/tools/builtin/ai/index.d.ts.map +1 -1
  99. package/dist/tools/builtin/ai/index.js +3 -5
  100. package/dist/tools/builtin/ai/index.js.map +1 -1
  101. package/dist/tools/builtin/index.d.ts +2 -0
  102. package/dist/tools/builtin/index.d.ts.map +1 -1
  103. package/dist/tools/builtin/index.js +2 -0
  104. package/dist/tools/builtin/index.js.map +1 -1
  105. package/dist/tools/builtin/nft/BaseNftMessageTool.d.ts +35 -0
  106. package/dist/tools/builtin/nft/BaseNftMessageTool.d.ts.map +1 -0
  107. package/dist/tools/builtin/nft/BaseNftMessageTool.js +45 -0
  108. package/dist/tools/builtin/nft/BaseNftMessageTool.js.map +1 -0
  109. package/dist/tools/builtin/nft/NFTContractInfoTool.d.ts +7 -13
  110. package/dist/tools/builtin/nft/NFTContractInfoTool.d.ts.map +1 -1
  111. package/dist/tools/builtin/nft/NFTContractInfoTool.js +8 -43
  112. package/dist/tools/builtin/nft/NFTContractInfoTool.js.map +1 -1
  113. package/dist/tools/builtin/nft/NFTMetadataTool.d.ts +7 -113
  114. package/dist/tools/builtin/nft/NFTMetadataTool.d.ts.map +1 -1
  115. package/dist/tools/builtin/nft/NFTMetadataTool.js +9 -148
  116. package/dist/tools/builtin/nft/NFTMetadataTool.js.map +1 -1
  117. package/dist/tools/builtin/nft/SendNftTool.d.ts +14 -0
  118. package/dist/tools/builtin/nft/SendNftTool.d.ts.map +1 -0
  119. package/dist/tools/builtin/nft/SendNftTool.js +20 -0
  120. package/dist/tools/builtin/nft/SendNftTool.js.map +1 -0
  121. package/dist/tools/builtin/nft/WalletNFTsTool.d.ts +7 -74
  122. package/dist/tools/builtin/nft/WalletNFTsTool.d.ts.map +1 -1
  123. package/dist/tools/builtin/nft/WalletNFTsTool.js +9 -161
  124. package/dist/tools/builtin/nft/WalletNFTsTool.js.map +1 -1
  125. package/dist/tools/builtin/pool/EstimatePoolYieldTool.d.ts.map +1 -1
  126. package/dist/tools/builtin/pool/EstimatePoolYieldTool.js +4 -3
  127. package/dist/tools/builtin/pool/EstimatePoolYieldTool.js.map +1 -1
  128. package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.d.ts +1 -0
  129. package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.d.ts.map +1 -1
  130. package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.js +8 -5
  131. package/dist/tools/builtin/pool/OpenAddLiquidityFormTool.js.map +1 -1
  132. package/dist/tools/builtin/pool/PoolByAddressTool.d.ts.map +1 -1
  133. package/dist/tools/builtin/pool/PoolByAddressTool.js +4 -3
  134. package/dist/tools/builtin/pool/PoolByAddressTool.js.map +1 -1
  135. package/dist/tools/builtin/pool/PoolDetailTool.d.ts.map +1 -1
  136. package/dist/tools/builtin/pool/PoolDetailTool.js +4 -3
  137. package/dist/tools/builtin/pool/PoolDetailTool.js.map +1 -1
  138. package/dist/tools/builtin/pool/PoolSearchTool.d.ts.map +1 -1
  139. package/dist/tools/builtin/pool/PoolSearchTool.js +4 -3
  140. package/dist/tools/builtin/pool/PoolSearchTool.js.map +1 -1
  141. package/dist/tools/builtin/pool/PreviewAddLiquidityTool.d.ts +1 -0
  142. package/dist/tools/builtin/pool/PreviewAddLiquidityTool.d.ts.map +1 -1
  143. package/dist/tools/builtin/pool/PreviewAddLiquidityTool.js +5 -3
  144. package/dist/tools/builtin/pool/PreviewAddLiquidityTool.js.map +1 -1
  145. package/dist/tools/builtin/pool/TopPoolsTool.d.ts.map +1 -1
  146. package/dist/tools/builtin/pool/TopPoolsTool.js +4 -3
  147. package/dist/tools/builtin/pool/TopPoolsTool.js.map +1 -1
  148. package/dist/tools/builtin/pool-subgraph/SubgraphCoinPoolPairsTool.d.ts +54 -0
  149. package/dist/tools/builtin/pool-subgraph/SubgraphCoinPoolPairsTool.d.ts.map +1 -0
  150. package/dist/tools/builtin/pool-subgraph/SubgraphCoinPoolPairsTool.js +98 -0
  151. package/dist/tools/builtin/pool-subgraph/SubgraphCoinPoolPairsTool.js.map +1 -0
  152. package/dist/tools/builtin/pool-subgraph/SubgraphPoolByAddressTool.d.ts +63 -0
  153. package/dist/tools/builtin/pool-subgraph/SubgraphPoolByAddressTool.d.ts.map +1 -0
  154. package/dist/tools/builtin/pool-subgraph/SubgraphPoolByAddressTool.js +82 -0
  155. package/dist/tools/builtin/pool-subgraph/SubgraphPoolByAddressTool.js.map +1 -0
  156. package/dist/tools/builtin/pool-subgraph/SubgraphPoolByPositionIdTool.d.ts +79 -0
  157. package/dist/tools/builtin/pool-subgraph/SubgraphPoolByPositionIdTool.d.ts.map +1 -0
  158. package/dist/tools/builtin/pool-subgraph/SubgraphPoolByPositionIdTool.js +97 -0
  159. package/dist/tools/builtin/pool-subgraph/SubgraphPoolByPositionIdTool.js.map +1 -0
  160. package/dist/tools/builtin/pool-subgraph/SubgraphPoolSearchTool.d.ts +77 -0
  161. package/dist/tools/builtin/pool-subgraph/SubgraphPoolSearchTool.d.ts.map +1 -0
  162. package/dist/tools/builtin/pool-subgraph/SubgraphPoolSearchTool.js +190 -0
  163. package/dist/tools/builtin/pool-subgraph/SubgraphPoolSearchTool.js.map +1 -0
  164. package/dist/tools/builtin/pool-subgraph/SubgraphPositionDetailTool.d.ts +107 -0
  165. package/dist/tools/builtin/pool-subgraph/SubgraphPositionDetailTool.d.ts.map +1 -0
  166. package/dist/tools/builtin/pool-subgraph/SubgraphPositionDetailTool.js +92 -0
  167. package/dist/tools/builtin/pool-subgraph/SubgraphPositionDetailTool.js.map +1 -0
  168. package/dist/tools/builtin/pool-subgraph/SubgraphTrendingPoolsTool.d.ts +56 -0
  169. package/dist/tools/builtin/pool-subgraph/SubgraphTrendingPoolsTool.d.ts.map +1 -0
  170. package/dist/tools/builtin/pool-subgraph/SubgraphTrendingPoolsTool.js +94 -0
  171. package/dist/tools/builtin/pool-subgraph/SubgraphTrendingPoolsTool.js.map +1 -0
  172. package/dist/tools/builtin/pool-subgraph/index.d.ts +13 -0
  173. package/dist/tools/builtin/pool-subgraph/index.d.ts.map +1 -0
  174. package/dist/tools/builtin/pool-subgraph/index.js +18 -0
  175. package/dist/tools/builtin/pool-subgraph/index.js.map +1 -0
  176. package/dist/tools/builtin/token/TokenAnalyticsTool.d.ts.map +1 -1
  177. package/dist/tools/builtin/token/TokenAnalyticsTool.js +4 -3
  178. package/dist/tools/builtin/token/TokenAnalyticsTool.js.map +1 -1
  179. package/dist/tools/builtin/token/TokenHoldersTool.d.ts.map +1 -1
  180. package/dist/tools/builtin/token/TokenHoldersTool.js +4 -3
  181. package/dist/tools/builtin/token/TokenHoldersTool.js.map +1 -1
  182. package/dist/tools/builtin/token/TokenInfoTool.d.ts +6 -2
  183. package/dist/tools/builtin/token/TokenInfoTool.d.ts.map +1 -1
  184. package/dist/tools/builtin/token/TokenInfoTool.js +66 -8
  185. package/dist/tools/builtin/token/TokenInfoTool.js.map +1 -1
  186. package/dist/tools/builtin/token/TokenScoreTool.d.ts.map +1 -1
  187. package/dist/tools/builtin/token/TokenScoreTool.js +4 -3
  188. package/dist/tools/builtin/token/TokenScoreTool.js.map +1 -1
  189. package/dist/tools/builtin/token/TopGainersTool.d.ts +10 -19
  190. package/dist/tools/builtin/token/TopGainersTool.d.ts.map +1 -1
  191. package/dist/tools/builtin/token/TopGainersTool.js +44 -54
  192. package/dist/tools/builtin/token/TopGainersTool.js.map +1 -1
  193. package/dist/tools/builtin/token/TopLosersTool.d.ts.map +1 -1
  194. package/dist/tools/builtin/token/TopLosersTool.js +3 -2
  195. package/dist/tools/builtin/token/TopLosersTool.js.map +1 -1
  196. package/dist/tools/builtin/token/TrendingTokensTool.d.ts +1 -1
  197. package/dist/tools/builtin/token/TrendingTokensTool.d.ts.map +1 -1
  198. package/dist/tools/builtin/token/TrendingTokensTool.js +20 -12
  199. package/dist/tools/builtin/token/TrendingTokensTool.js.map +1 -1
  200. package/dist/tools/builtin/token/index.d.ts +0 -2
  201. package/dist/tools/builtin/token/index.d.ts.map +1 -1
  202. package/dist/tools/builtin/token/index.js +1 -3
  203. package/dist/tools/builtin/token/index.js.map +1 -1
  204. package/dist/tools/builtin/wallet/TransactionByHashTool.d.ts.map +1 -1
  205. package/dist/tools/builtin/wallet/TransactionByHashTool.js +4 -3
  206. package/dist/tools/builtin/wallet/TransactionByHashTool.js.map +1 -1
  207. package/dist/tools/builtin/wallet/WalletApprovalsTool.d.ts.map +1 -1
  208. package/dist/tools/builtin/wallet/WalletApprovalsTool.js +4 -3
  209. package/dist/tools/builtin/wallet/WalletApprovalsTool.js.map +1 -1
  210. package/dist/tools/builtin/wallet/WalletDefiPositionsTool.d.ts.map +1 -1
  211. package/dist/tools/builtin/wallet/WalletDefiPositionsTool.js +4 -3
  212. package/dist/tools/builtin/wallet/WalletDefiPositionsTool.js.map +1 -1
  213. package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.d.ts.map +1 -1
  214. package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.js +4 -3
  215. package/dist/tools/builtin/wallet/WalletDefiProtocolPositionsTool.js.map +1 -1
  216. package/dist/tools/builtin/wallet/WalletDefiSummaryTool.d.ts.map +1 -1
  217. package/dist/tools/builtin/wallet/WalletDefiSummaryTool.js +4 -3
  218. package/dist/tools/builtin/wallet/WalletDefiSummaryTool.js.map +1 -1
  219. package/dist/tools/builtin/wallet/WalletHistoryTool.d.ts.map +1 -1
  220. package/dist/tools/builtin/wallet/WalletHistoryTool.js +5 -4
  221. package/dist/tools/builtin/wallet/WalletHistoryTool.js.map +1 -1
  222. package/dist/tools/builtin/wallet/WalletNetWorthTool.js +1 -1
  223. package/dist/tools/builtin/wallet/WalletNetWorthTool.js.map +1 -1
  224. package/dist/tools/builtin/wallet/WalletNftTransfersTool.d.ts.map +1 -1
  225. package/dist/tools/builtin/wallet/WalletNftTransfersTool.js +5 -4
  226. package/dist/tools/builtin/wallet/WalletNftTransfersTool.js.map +1 -1
  227. package/dist/tools/builtin/wallet/WalletPnlSummaryTool.d.ts.map +1 -1
  228. package/dist/tools/builtin/wallet/WalletPnlSummaryTool.js +4 -3
  229. package/dist/tools/builtin/wallet/WalletPnlSummaryTool.js.map +1 -1
  230. package/dist/tools/builtin/wallet/WalletPnlTool.d.ts.map +1 -1
  231. package/dist/tools/builtin/wallet/WalletPnlTool.js +4 -3
  232. package/dist/tools/builtin/wallet/WalletPnlTool.js.map +1 -1
  233. package/dist/tools/builtin/wallet/WalletTokenBalancesTool.d.ts.map +1 -1
  234. package/dist/tools/builtin/wallet/WalletTokenBalancesTool.js +4 -3
  235. package/dist/tools/builtin/wallet/WalletTokenBalancesTool.js.map +1 -1
  236. package/dist/tools/builtin/wallet/WalletTokenTransfersTool.d.ts.map +1 -1
  237. package/dist/tools/builtin/wallet/WalletTokenTransfersTool.js +5 -4
  238. package/dist/tools/builtin/wallet/WalletTokenTransfersTool.js.map +1 -1
  239. package/dist/tools/builtin/wallet/index.d.ts +3 -0
  240. package/dist/tools/builtin/wallet/index.d.ts.map +1 -1
  241. package/dist/tools/builtin/wallet/index.js +5 -1
  242. package/dist/tools/builtin/wallet/index.js.map +1 -1
  243. package/dist/tools/builtin/wallet-action/ApproveTokenTool.d.ts +25 -0
  244. package/dist/tools/builtin/wallet-action/ApproveTokenTool.d.ts.map +1 -0
  245. package/dist/tools/builtin/wallet-action/ApproveTokenTool.js +98 -0
  246. package/dist/tools/builtin/wallet-action/ApproveTokenTool.js.map +1 -0
  247. package/dist/tools/builtin/wallet-action/BaseWalletActionTool.d.ts +211 -0
  248. package/dist/tools/builtin/wallet-action/BaseWalletActionTool.d.ts.map +1 -0
  249. package/dist/tools/builtin/wallet-action/BaseWalletActionTool.js +499 -0
  250. package/dist/tools/builtin/wallet-action/BaseWalletActionTool.js.map +1 -0
  251. package/dist/tools/builtin/wallet-action/BuyTokenTool.d.ts +240 -0
  252. package/dist/tools/builtin/wallet-action/BuyTokenTool.d.ts.map +1 -0
  253. package/dist/tools/builtin/wallet-action/BuyTokenTool.js +1257 -0
  254. package/dist/tools/builtin/wallet-action/BuyTokenTool.js.map +1 -0
  255. package/dist/tools/builtin/wallet-action/SendNativeTool.d.ts +41 -0
  256. package/dist/tools/builtin/wallet-action/SendNativeTool.d.ts.map +1 -0
  257. package/dist/tools/builtin/wallet-action/SendNativeTool.js +127 -0
  258. package/dist/tools/builtin/wallet-action/SendNativeTool.js.map +1 -0
  259. package/dist/tools/builtin/wallet-action/SendTokenTool.d.ts +63 -0
  260. package/dist/tools/builtin/wallet-action/SendTokenTool.d.ts.map +1 -0
  261. package/dist/tools/builtin/wallet-action/SendTokenTool.js +294 -0
  262. package/dist/tools/builtin/wallet-action/SendTokenTool.js.map +1 -0
  263. package/dist/tools/builtin/wallet-action/SwapTokenTool.d.ts +247 -0
  264. package/dist/tools/builtin/wallet-action/SwapTokenTool.d.ts.map +1 -0
  265. package/dist/tools/builtin/wallet-action/SwapTokenTool.js +1258 -0
  266. package/dist/tools/builtin/wallet-action/SwapTokenTool.js.map +1 -0
  267. package/dist/tools/builtin/wallet-action/UnwrapNativeTool.d.ts +20 -0
  268. package/dist/tools/builtin/wallet-action/UnwrapNativeTool.d.ts.map +1 -0
  269. package/dist/tools/builtin/wallet-action/UnwrapNativeTool.js +36 -0
  270. package/dist/tools/builtin/wallet-action/UnwrapNativeTool.js.map +1 -0
  271. package/dist/tools/builtin/wallet-action/WrapNativeTool.d.ts +23 -0
  272. package/dist/tools/builtin/wallet-action/WrapNativeTool.d.ts.map +1 -0
  273. package/dist/tools/builtin/wallet-action/WrapNativeTool.js +54 -0
  274. package/dist/tools/builtin/wallet-action/WrapNativeTool.js.map +1 -0
  275. package/dist/tools/builtin/wallet-action/amountSpec.d.ts +62 -0
  276. package/dist/tools/builtin/wallet-action/amountSpec.d.ts.map +1 -0
  277. package/dist/tools/builtin/wallet-action/amountSpec.js +93 -0
  278. package/dist/tools/builtin/wallet-action/amountSpec.js.map +1 -0
  279. package/dist/tools/builtin/wallet-action/gasReserve.d.ts +42 -0
  280. package/dist/tools/builtin/wallet-action/gasReserve.d.ts.map +1 -0
  281. package/dist/tools/builtin/wallet-action/gasReserve.js +103 -0
  282. package/dist/tools/builtin/wallet-action/gasReserve.js.map +1 -0
  283. package/dist/tools/builtin/wallet-action/index.d.ts +9 -0
  284. package/dist/tools/builtin/wallet-action/index.d.ts.map +1 -0
  285. package/dist/tools/builtin/wallet-action/index.js +20 -0
  286. package/dist/tools/builtin/wallet-action/index.js.map +1 -0
  287. package/dist/tools/chainResolver.d.ts +98 -0
  288. package/dist/tools/chainResolver.d.ts.map +1 -0
  289. package/dist/tools/chainResolver.js +302 -0
  290. package/dist/tools/chainResolver.js.map +1 -0
  291. package/dist/types/index.d.ts +218 -7
  292. package/dist/types/index.d.ts.map +1 -1
  293. package/package.json +5 -2
  294. package/dist/tools/builtin/ai/MoralisCortexTool.d.ts +0 -33
  295. package/dist/tools/builtin/ai/MoralisCortexTool.d.ts.map +0 -1
  296. package/dist/tools/builtin/ai/MoralisCortexTool.js +0 -76
  297. package/dist/tools/builtin/ai/MoralisCortexTool.js.map +0 -1
  298. package/dist/tools/builtin/ai/SolanaCortexTool.d.ts +0 -22
  299. package/dist/tools/builtin/ai/SolanaCortexTool.d.ts.map +0 -1
  300. package/dist/tools/builtin/ai/SolanaCortexTool.js +0 -80
  301. package/dist/tools/builtin/ai/SolanaCortexTool.js.map +0 -1
@@ -0,0 +1,1257 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BuyTokenTool = void 0;
4
+ const BaseTool_1 = require("../../BaseTool");
5
+ const web3_1 = require("../../../functions/web3");
6
+ const MoralisService_1 = require("../../../services/MoralisService");
7
+ const PantographService_1 = require("../../../services/PantographService");
8
+ const swap_1 = require("../../../services/swap");
9
+ const PoolService_1 = require("../../../services/PoolService");
10
+ const abi_1 = require("../../../constants/abi");
11
+ const amountSpec_1 = require("./amountSpec");
12
+ const gasReserve_1 = require("./gasReserve");
13
+ const chainResolver_1 = require("../../chainResolver");
14
+ /**
15
+ * Tool: open-buy-token-form
16
+ *
17
+ * A STANDALONE wallet-action tool (NOT a `BaseWalletActionTool`): instead of
18
+ * emitting the shared form payload, it does the quote + approval work itself and
19
+ * ships a custom `BuyTokenConfirmTx` UI payload the FE executes directly. That's
20
+ * why `buy_token` is intentionally NOT a member of `WalletActionType`.
21
+ *
22
+ * Branches, picked deterministically from the args:
23
+ *
24
+ * 0. **Trending picker** — the user wants to buy but did NOT name a token
25
+ * (`token_symbol` empty). The tool fetches the chain's live trending list
26
+ * and emits `actionButtons` so the user picks one. A click submits a new
27
+ * prompt like `"buy PEPE"` which routes back here for the standard flow.
28
+ *
29
+ * 1. **Pay-token picker** — the user named the dest token only (`token_symbol`)
30
+ * but not the pay token. The tool fetches the wallet's native + ERC-20
31
+ * holdings and emits `actionButtons` so the user picks the pay token.
32
+ *
33
+ * 1.4 **Balance check** — the user named a pay token (amount optional). Runs
34
+ * first so we never proceed with a token the user can't fund: if the wallet
35
+ * holds NONE of it, the tool reports that and offers the tokens the user
36
+ * DOES hold as alternatives (pay-token picker); if it holds SOME but less
37
+ * than the requested amount, it reports the shortfall and emits
38
+ * 25/50/75/100% buttons sized off the real balance. Skipped for the native
39
+ * pay token and when balances can't be read.
40
+ *
41
+ * 1.5 **Amount picker** — dest + pay token are known but no amount was given.
42
+ * The tool reads the pay-token balance and emits 25/50/75/100% buttons so
43
+ * the user sizes the spend in one tap (or types a custom amount).
44
+ *
45
+ * 1.75 **Confirm tx** — dest + pay token + a PAY-TOKEN amount are all known.
46
+ * The tool fetches a swap quote (estimated receive amount + the raw swap
47
+ * tx), checks allowance, and emits a `BuyTokenConfirmTx` payload bundling
48
+ * the estimate, the raw swap transaction, and the approve transaction (when
49
+ * one is needed). The FE executes approve (if any) then swap itself.
50
+ *
51
+ * When none of the above produces a result (couldn't resolve the dest token,
52
+ * quote failed, amount given only on the buy side, …) the tool returns an
53
+ * error/_instructions object — it never opens a generic form.
54
+ *
55
+ * Amount disambiguation mirrors the chatbot SDK:
56
+ * - "buy PEPE with 10 USDC" → pay_with_amount = "10"
57
+ * - "buy 0.02 PCM with USDC" → buy_amount = "0.02"
58
+ */
59
+ class BuyTokenTool extends BaseTool_1.BaseTool {
60
+ name = 'open-buy-token-form';
61
+ kind = 'action';
62
+ category = 'wallet-action';
63
+ // The tool drives a concrete confirm/pick step — no generic LLM follow-ups.
64
+ noSuggestions = true;
65
+ moralis;
66
+ /** Trending tokens come from Pantograph (top-gainers), not Moralis. */
67
+ pantograph;
68
+ /** Branch-tracing on/off — shares AgentCore's `debug` flag (passed in). */
69
+ debug;
70
+ constructor(config, options) {
71
+ super();
72
+ if (config !== undefined) {
73
+ this.moralis = new MoralisService_1.MoralisService(config);
74
+ this.pantograph = new PantographService_1.PantographService({ baseUrl: config.pantographUrl });
75
+ }
76
+ this.debug = options?.debug ?? false;
77
+ }
78
+ /**
79
+ * Branch-tracing logger. Prints which branch the tool takes and the key
80
+ * values feeding that decision. Enabled by AgentCore's shared `debug` flag
81
+ * (so one switch covers the LLM trace and this tool). Kept terse.
82
+ */
83
+ dbg(label, data) {
84
+ if (this.debug) {
85
+ if (data === undefined)
86
+ console.log(`[BuyTokenTool] ${label}`);
87
+ else
88
+ console.log(`[BuyTokenTool] ${label}`, JSON.stringify(data));
89
+ }
90
+ }
91
+ description = 'Open the BUY TOKEN flow for ANY buy/purchase intent. Handles both cases: ' +
92
+ '(a) the user NAMED a token — "buy PEPE", "mua DOGE", "buy 100 USDC with ETH": pass `token_symbol` ' +
93
+ '(the destination); if you also know the pay token, fill `pay_with_symbol` (or `pay_with_address`), ' +
94
+ 'else leave pay_with_* blank and the tool returns a wallet picker. ' +
95
+ '(b) the user did NOT name a token — "buy a trending token", "mua token trending", "buy something ' +
96
+ 'pumping", "buy hot tokens", "mua token tăng mạnh": leave `token_symbol` blank and the tool returns ' +
97
+ "buttons of the chain's current trending tokens so the user picks one. " +
98
+ 'Amount: `buy_amount` = destination to RECEIVE; `pay_with_amount` = payment to SPEND. Never both.';
99
+ parameters = [
100
+ { name: 'chain', type: 'string', description: 'Hex chain id. Defaults to connected chain.', required: false },
101
+ {
102
+ name: 'token_symbol',
103
+ type: 'string',
104
+ description: 'Symbol of the token the user wants to BUY (e.g. "PEPE"). Leave EMPTY when the user did not name ' +
105
+ 'a token (e.g. "buy a trending token") — the tool then returns trending tokens to pick from.',
106
+ required: false,
107
+ },
108
+ {
109
+ name: 'contract_address',
110
+ type: 'string',
111
+ description: 'Destination token address (0x…), or "native" for the chain\'s native coin. Leave empty when ' +
112
+ 'unknown — the tool resolves it from token_symbol.',
113
+ required: false,
114
+ },
115
+ {
116
+ name: 'buy_amount',
117
+ type: 'string',
118
+ description: 'Quantity of the DESTINATION token (the one being bought) the user wants to RECEIVE. ' +
119
+ 'Set this when the amount sits next to the token being bought: "buy 5 PEPE", "mua 5 PEPE", ' +
120
+ '"buy 0.02 PCM with USDC" → buy_amount = the number next to PEPE/PCM. ' +
121
+ 'May also be a USD value when the user sizes the buy in dollars ("buy $5 of PEPE" → "5$"); ' +
122
+ 'pass the user\'s expression VERBATIM (the tool converts "$" to a token quantity). ' +
123
+ 'Mutually exclusive with pay_with_amount — set at most ONE of the two.',
124
+ required: false,
125
+ },
126
+ {
127
+ name: 'pay_with_symbol',
128
+ type: 'string',
129
+ description: 'Symbol of the payment token (e.g. "USDC", "ETH").',
130
+ required: false,
131
+ },
132
+ {
133
+ name: 'pay_with_address',
134
+ type: 'string',
135
+ description: 'Address of the payment token (0x…), OR "native" for the chain\'s native coin.',
136
+ required: false,
137
+ },
138
+ {
139
+ name: 'pay_with_amount',
140
+ type: 'string',
141
+ description: 'Quantity of the PAYMENT token (the one being spent) the user wants to SPEND. ' +
142
+ 'Set this when the amount sits next to the payment token: "buy PEPE with 10 USDC", ' +
143
+ '"mua PEPE bằng 10 USDC", "spend 0.5 ETH on DOGE" → pay_with_amount = the number next to USDC/ETH. ' +
144
+ 'May also be a PERCENT of the pay-token balance ("buy USDC with 50% USDT" → "50%"), the literal word ' +
145
+ '"max" meaning the WHOLE balance, or a USD value ("buy USDC with 5$ USDT" → "5$"). NORMALIZE any ' +
146
+ '"whole balance / everything / maximum" phrasing in ANY language (e.g. "all", "max", "tất cả", ' +
147
+ '"toàn bộ", "全部", "すべて", "전부", "todo", "tout") to the literal "max"; pass plain/percent/USD ' +
148
+ 'numbers VERBATIM (the tool converts "%"/"$"/"max"). "max" is an AMOUNT, never a token — still set ' +
149
+ 'pay_with_symbol to the payment token ("ETH" in "buy USDC with max ETH"). ' +
150
+ 'Mutually exclusive with buy_amount — set at most ONE of the two.',
151
+ required: false,
152
+ },
153
+ {
154
+ name: 'limit',
155
+ type: 'number',
156
+ description: 'Only used when no token is named: max number of trending tokens to show as buttons. Default 6. Clamped to [1, 10].',
157
+ required: false,
158
+ },
159
+ {
160
+ name: 'buy_prompt_template',
161
+ type: 'string',
162
+ description: 'A short "buy" command IN THE USER\'S CURRENT LANGUAGE, used as the click-command for the trending-token ' +
163
+ 'picker (when the user named no token). Use the exact placeholder "{token}" (do not translate the braces) ' +
164
+ 'for the token symbol; put the localized verb directly in the text. ' +
165
+ 'English example: "buy {token}". Vietnamese example: "mua {token}". Japanese example: "{token} を買う". ' +
166
+ 'Always include {token}.',
167
+ required: true,
168
+ },
169
+ {
170
+ name: 'buy_with_prompt_template',
171
+ type: 'string',
172
+ description: 'A short "buy X with Y" command IN THE USER\'S CURRENT LANGUAGE, used as the click-command for the ' +
173
+ 'pay-token picker and the percentage-spend buttons. Use these exact placeholders (do not translate the ' +
174
+ 'braces): "{token}" = token being bought, "{pay}" = payment token, "{buy_amount}" = quantity of the ' +
175
+ 'BOUGHT token (place it next to {token}), "{pay_amount}" = quantity of the PAYMENT token (place it next ' +
176
+ 'to {pay}). Put the localized verb and the "with" preposition directly in the text. ' +
177
+ 'English example: "buy {buy_amount} {token} with {pay_amount} {pay}". ' +
178
+ 'Vietnamese example: "mua {buy_amount} {token} bằng {pay_amount} {pay}". ' +
179
+ 'Japanese example: "{pay_amount} {pay} で {buy_amount} {token} を買う". ' +
180
+ 'Always include {token} and {pay}. Include both {buy_amount} and {pay_amount} too — the tool removes ' +
181
+ 'whichever amount is unknown (only one side ever carries an amount).',
182
+ required: true,
183
+ },
184
+ ];
185
+ // -------------------------------------------------------------------------
186
+ // execute: lift `ui` / `actionButtons` from run() onto the ToolResult.
187
+ // -------------------------------------------------------------------------
188
+ async execute(args, userContext) {
189
+ const callId = `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
190
+ const start = Date.now();
191
+ try {
192
+ const out = (await this.run(args, userContext));
193
+ const result = {
194
+ toolName: this.name,
195
+ callId,
196
+ success: true,
197
+ data: out,
198
+ duration: Date.now() - start,
199
+ };
200
+ if (out && typeof out === 'object' && out.ui) {
201
+ result.ui = out.ui;
202
+ }
203
+ if (out && Array.isArray(out.actionButtons) && out.actionButtons.length > 0) {
204
+ result.actionButtons = out.actionButtons;
205
+ }
206
+ return result;
207
+ }
208
+ catch (err) {
209
+ return {
210
+ toolName: this.name,
211
+ callId,
212
+ success: false,
213
+ error: err instanceof Error ? err.message : String(err),
214
+ duration: Date.now() - start,
215
+ };
216
+ }
217
+ }
218
+ async run(args, userContext) {
219
+ const walletAddress = userContext?.walletAddress;
220
+ if (!walletAddress) {
221
+ return {
222
+ error: 'wallet_not_connected',
223
+ _instructions: 'The user is not connected. Ask them to connect their wallet before buying. Do NOT proceed.',
224
+ };
225
+ }
226
+ // The wallet can only sign on the chain it's connected to. If the user named
227
+ // a chain that differs from their connected chain, ask them to switch first
228
+ // instead of building a buy they can't sign.
229
+ const mismatch = (0, chainResolver_1.detectChainMismatch)(args.chain, userContext);
230
+ if (mismatch) {
231
+ return {
232
+ error: 'wrong_chain',
233
+ _instructions: `The user asked to buy on ${mismatch.requestedLabel}, but their wallet is connected to ${mismatch.connectedLabel}. ` +
234
+ `Tell them, in their language, that to buy on ${mismatch.requestedLabel} they must first switch their wallet's network to ${mismatch.requestedLabel}, ` +
235
+ `or they can buy on ${mismatch.connectedLabel} (their current network) instead. Do NOT proceed. Do NOT mention tool names, UI, or forms.`,
236
+ };
237
+ }
238
+ const chainId = this.requireChain(args, userContext);
239
+ if (!chainId) {
240
+ return {
241
+ error: 'missing_chain',
242
+ _instructions: 'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.',
243
+ };
244
+ }
245
+ return this.buildResult(args, userContext);
246
+ }
247
+ // -------------------------------------------------------------------------
248
+ // Main branch dispatch
249
+ // -------------------------------------------------------------------------
250
+ async buildResult(args, userContext) {
251
+ const symbol = typeof args.token_symbol === 'string' ? args.token_symbol.trim() : '';
252
+ const destRef = typeof args.contract_address === 'string' ? args.contract_address.trim() : '';
253
+ const chain = this.requireChain(args, userContext) ?? undefined;
254
+ const walletAddress = userContext?.walletAddress ?? undefined;
255
+ this.dbg('buildResult:args', { args, chain, walletAddress, hasMoralis: !!this.moralis });
256
+ // ----- Branch 0: TRENDING PICKER — no token named at all ---------------
257
+ if (!symbol && !destRef) {
258
+ this.dbg('→ Branch 0: trending picker (no token named)');
259
+ return await this.buildTrendingPicker(args, userContext);
260
+ }
261
+ // ----- Resolve DEST token (symbol/address → address + decimals) --------
262
+ const destSide = await this.resolveSide(args.contract_address, symbol, chain, walletAddress, 'dest');
263
+ const destContract = destSide.address;
264
+ const destSymbol = destSide.symbol ?? symbol;
265
+ const destDecimals = destSide.decimals;
266
+ // ----- Resolve PAY token (if user gave one) ----------------------------
267
+ const paySide = await this.resolveSide(args.pay_with_address, args.pay_with_symbol, chain, walletAddress, 'pay');
268
+ const payAddr = paySide.address;
269
+ const paySymbol = paySide.symbol;
270
+ const payDecimals = paySide.decimals;
271
+ // Amounts may be plain ("10"), a percent of balance ("50%"), or a USD value
272
+ // ("5$"). Pay-side percent/usd resolve against the PAY token's balance/price;
273
+ // buy-side usd resolves against the DEST token's price (percent on the buy
274
+ // side is meaningless, so it's ignored). Pay-side resolution needs payAddr —
275
+ // when it's missing the pay picker runs first and re-submits with a token.
276
+ const buyAmount = await this.resolveBuyAmountSpec(args.buy_amount, destContract, chain, destSymbol);
277
+ let payAmount = payAddr
278
+ ? await this.resolvePayAmountSpec(args.pay_with_amount, payAddr, chain, walletAddress, paySymbol)
279
+ : this.normaliseAmount(args.pay_with_amount);
280
+ // Whether the pay amount was sized off the balance (percent / "max" / usd):
281
+ // such an amount is ≤ spendable by construction, so the balance check skips
282
+ // the over-balance comparison for it (a re-read spendable can drift a hair).
283
+ const paySpec = (0, amountSpec_1.parseAmountSpec)(args.pay_with_amount);
284
+ let amountSizedToBalance = !!paySpec && paySpec.kind !== 'token';
285
+ // Set to the buy-side amount once we convert it into `payAmount` (Branch 1.3)
286
+ // so downstream messaging can explain the conversion rather than pretend the
287
+ // user typed the pay amount.
288
+ let derivedFromBuy = null;
289
+ this.dbg('resolved sides', {
290
+ dest: { destContract, destSymbol, destDecimals },
291
+ pay: { payAddr, paySymbol, payDecimals },
292
+ buyAmount,
293
+ payAmount,
294
+ });
295
+ // Could not resolve the destination token — surface it (never open a form).
296
+ if (!destContract) {
297
+ this.dbg('→ dest token not resolved');
298
+ return {
299
+ error: 'dest_token_not_found',
300
+ _instructions: `Could not find the token "${destSymbol || destRef}" to buy on this chain. ` +
301
+ 'Tell the user, in their language, that the token could not be found and ask them to check the name ' +
302
+ 'or provide its contract address. Do NOT invent an address. Do NOT mention tool names, UI, or forms.',
303
+ };
304
+ }
305
+ // ----- Branch 1: PAY PICKER — dest known, pay missing ------------------
306
+ if (!payAddr && this.moralis && userContext?.walletAddress) {
307
+ this.dbg('→ Branch 1: pay picker (pay token missing)');
308
+ return await this.buildPayWithPicker({ args, userContext, destSymbol, destContract, buyAmount, payAmount });
309
+ }
310
+ if (!payAddr) {
311
+ return {
312
+ error: 'pay_token_missing',
313
+ _instructions: `Ask the user, in their language, which token they want to spend to buy ${destSymbol}. ` +
314
+ 'Do NOT invent a token. Do NOT mention tool names, UI, or forms.',
315
+ };
316
+ }
317
+ // ----- Branch 1.3: BUY-SIDE AMOUNT → PAY AMOUNT ------------------------
318
+ // deBridge only supports EXACT_INPUT (spend X, receive ~Y). When the user
319
+ // sized the BUY side instead ("mua 5 PEPE", "mua 5 PEPE bằng USDC") we have
320
+ // no pay amount to seed the quote — derive one from the live USD price ratio
321
+ // (payAmount ≈ buyAmount × destPrice / payPrice, +2% buffer for slippage/fees
322
+ // so they receive AT LEAST about the requested amount). The derived amount
323
+ // then flows through the SAME balance-check + confirm-tx path below.
324
+ if (buyAmount && !payAmount) {
325
+ this.dbg('Branch 1.3: deriving pay amount from buy amount', { buyAmount, destSymbol, paySymbol });
326
+ const derived = await this.derivePayAmountFromBuy({
327
+ chain,
328
+ buyAmount,
329
+ destContract,
330
+ destSymbol,
331
+ payAddr,
332
+ paySymbol,
333
+ });
334
+ if (!derived) {
335
+ // No reliable USD price for one of the tokens — fall back to asking how
336
+ // much of the pay token to spend (we can't convert the buy side).
337
+ this.dbg('→ Branch 1.3: price unavailable — needs pay amount');
338
+ return {
339
+ error: 'needs_pay_amount',
340
+ _instructions: `Could not work out how much ${paySymbol ?? 'of the payment token'} equals ${buyAmount} ${destSymbol} right now. ` +
341
+ `Tell the user, in their language, that the price couldn't be fetched at the moment, and ask how much ${paySymbol ?? 'of the payment token'} they want to spend instead. ` +
342
+ 'Do NOT invent an amount or a price. Do NOT mention tool names, UI, or forms.',
343
+ };
344
+ }
345
+ payAmount = derived;
346
+ derivedFromBuy = buyAmount;
347
+ // A derived amount is a real cost that CAN exceed the balance — it must be
348
+ // checked (it was sized off the buy amount, not off the pay balance).
349
+ amountSizedToBalance = false;
350
+ this.dbg('→ Branch 1.3: derived pay amount', { payAmount, derivedFromBuy });
351
+ }
352
+ // ----- Branch 1.4: BALANCE CHECK — pay token named ---------------------
353
+ // Native is included: its spendable balance (held − gas reserve) is read by
354
+ // readPayBalance, so an amount that wouldn't leave room for gas is caught
355
+ // here just like an over-balance ERC-20 amount.
356
+ if (this.moralis && userContext?.walletAddress) {
357
+ this.dbg('Branch 1.4: checking pay balance', { payAddr, paySymbol, payAmount });
358
+ const shortfall = await this.checkPayBalance({
359
+ args,
360
+ userContext,
361
+ chain,
362
+ destSymbol,
363
+ destContract,
364
+ payAddr,
365
+ paySymbol,
366
+ payAmount,
367
+ buyAmount,
368
+ derivedFromBuy,
369
+ amountSizedToBalance,
370
+ });
371
+ if (shortfall) {
372
+ this.dbg('→ Branch 1.4: balance shortfall — returning', {
373
+ error: shortfall.error,
374
+ hasButtons: Array.isArray(shortfall.actionButtons),
375
+ });
376
+ return shortfall;
377
+ }
378
+ this.dbg('Branch 1.4: balance OK — continuing');
379
+ }
380
+ // ----- Branch 1.5: AMOUNT PICKER — dest + pay known, no amount ----------
381
+ if (!buyAmount && !payAmount && this.moralis && userContext?.walletAddress) {
382
+ this.dbg('Branch 1.5: building amount picker');
383
+ const picker = await this.buildAmountPicker({
384
+ userContext,
385
+ chain,
386
+ destSymbol,
387
+ payAddr,
388
+ paySymbol,
389
+ buyWithTemplate: typeof args.buy_with_prompt_template === 'string' ? args.buy_with_prompt_template : '',
390
+ });
391
+ if (picker) {
392
+ this.dbg('→ Branch 1.5: amount picker returned');
393
+ return picker;
394
+ }
395
+ this.dbg('Branch 1.5: amount picker null — needs amount');
396
+ }
397
+ // ----- Branch 1.75: BUILD CONFIRM TX -----------------------------------
398
+ // Everything is known AND the spend is expressed as a pay-token amount
399
+ // (EXACT_INPUT): estimate, build raw swap tx, check approval, ship the cube.
400
+ if (payAmount && (payDecimals !== undefined || payAddr === 'native')) {
401
+ this.dbg('Branch 1.75: building confirm tx');
402
+ const confirm = await this.buildConfirmTx({
403
+ userContext,
404
+ chain,
405
+ destContract,
406
+ destSymbol,
407
+ destDecimals,
408
+ payAddr,
409
+ paySymbol,
410
+ payDecimals,
411
+ payAmount,
412
+ });
413
+ if (confirm && 'ui' in confirm) {
414
+ this.dbg('→ Branch 1.75: confirm tx returned');
415
+ return confirm;
416
+ }
417
+ // The swap provider returned a concrete reason — relay it to the user
418
+ // (it explains WHY, e.g. amount too small, no route, min/max limits).
419
+ if (confirm && 'quoteError' in confirm) {
420
+ this.dbg('→ Branch 1.75: quote error from provider', { quoteError: confirm.quoteError });
421
+ return {
422
+ error: 'quote_failed',
423
+ quoteError: confirm.quoteError,
424
+ _instructions: `Could not quote buying ${destSymbol} with ${paySymbol ?? 'the selected token'}. The swap provider reported: "${confirm.quoteError}". ` +
425
+ '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. ' +
426
+ 'Do NOT invent numbers or a different reason. Do NOT mention tool names, UI, or forms.',
427
+ };
428
+ }
429
+ // null — couldn't even attempt a quote (bad chain, missing decimals, …).
430
+ this.dbg('Branch 1.75: confirm tx unavailable (null)');
431
+ return {
432
+ error: 'quote_failed',
433
+ _instructions: `Could not get a quote to buy ${destSymbol} with ${paySymbol ?? 'the selected token'} right now. ` +
434
+ 'Tell the user, in their language, that the swap could not be quoted at the moment and ask them to try ' +
435
+ 'again shortly or with a different amount. Do NOT invent numbers. Do NOT mention tool names, UI, or forms.',
436
+ };
437
+ }
438
+ // The only thing missing is a spendable amount (or it was given on the buy
439
+ // side, which EXACT_INPUT can't seed off). Ask the user to size the spend.
440
+ this.dbg('→ needs pay amount');
441
+ return {
442
+ error: 'needs_pay_amount',
443
+ _instructions: `Ask the user, in their language, how much ${paySymbol ?? 'of the payment token'} they want to spend to buy ${destSymbol}. ` +
444
+ 'Do NOT invent an amount. Do NOT mention tool names, UI, or forms.',
445
+ };
446
+ }
447
+ // -------------------------------------------------------------------------
448
+ // Token resolution (standalone — no BaseWalletActionTool dependency)
449
+ // -------------------------------------------------------------------------
450
+ normaliseAddress(value) {
451
+ if (typeof value !== 'string')
452
+ return null;
453
+ const v = value.trim();
454
+ return (0, web3_1.isAddressEVM)(v) ? v : null;
455
+ }
456
+ normaliseAmount(value) {
457
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0)
458
+ return String(value);
459
+ if (typeof value !== 'string')
460
+ return null;
461
+ const v = value.trim();
462
+ if (!v)
463
+ return null;
464
+ const n = parseFloat(v);
465
+ return Number.isFinite(n) && n > 0 ? v : null;
466
+ }
467
+ requireChain(args, userContext) {
468
+ const fromArgs = typeof args.chain === 'string' && args.chain.trim() ? args.chain.trim() : null;
469
+ if (fromArgs)
470
+ return fromArgs;
471
+ return typeof userContext?.chain === 'string' && userContext.chain.trim() ? userContext.chain.trim() : null;
472
+ }
473
+ /**
474
+ * Resolve a token reference (symbol "USDC" or address "0x…") on a chain.
475
+ * Wallet balances first (most reliable), then Pantograph search. Decimals come
476
+ * from the API — never from the LLM.
477
+ */
478
+ async resolveContractAddress(key, chain, walletAddress) {
479
+ if (!this.moralis || !key)
480
+ return undefined;
481
+ const keyLower = key.trim().toLowerCase();
482
+ const keyIsAddress = (0, web3_1.isAddressEVM)(key.trim());
483
+ const matches = (t) => keyIsAddress ? (t.token_address ?? '').toLowerCase() === keyLower : (t.symbol ?? '').toLowerCase() === keyLower;
484
+ if (walletAddress) {
485
+ const balRes = await this.moralis.getWalletTokenBalances({ address: walletAddress, chain });
486
+ if (balRes.success && balRes.data?.result?.length) {
487
+ const match = balRes.data.result.find(matches);
488
+ if (match) {
489
+ return {
490
+ address: match.token_address,
491
+ symbol: match.symbol,
492
+ name: match.name,
493
+ decimals: typeof match.decimals === 'number' ? match.decimals : undefined,
494
+ };
495
+ }
496
+ }
497
+ }
498
+ const searchRes = await this.moralis.searchTokensByKey({ key, chain });
499
+ if (searchRes.success && searchRes.data?.length) {
500
+ const exact = searchRes.data.find(matches);
501
+ const candidate = exact ?? searchRes.data[0];
502
+ if (candidate?.token_address) {
503
+ return {
504
+ address: candidate.token_address,
505
+ symbol: candidate.symbol,
506
+ name: candidate.name,
507
+ decimals: typeof candidate.decimals === 'number' ? candidate.decimals : undefined,
508
+ };
509
+ }
510
+ }
511
+ return undefined;
512
+ }
513
+ /**
514
+ * Resolve one side of the buy (dest or pay). Honours the `"native"` sentinel
515
+ * the swap providers expect. Never errors — a side that can't be resolved
516
+ * just comes back with `address: null`.
517
+ */
518
+ async resolveSide(refArg, symbolArg, chain, walletAddress, label = 'side') {
519
+ const refStr = typeof refArg === 'string' ? refArg.trim() : '';
520
+ const symbolStr = typeof symbolArg === 'string' ? symbolArg.trim() : '';
521
+ if (refStr.toLowerCase() === 'native' || symbolStr.toLowerCase() === 'native') {
522
+ return { address: 'native', symbol: symbolStr || undefined };
523
+ }
524
+ const directAddress = this.normaliseAddress(refArg);
525
+ const symbolHint = symbolStr || undefined;
526
+ // The native coin surfaces as the zero address (in wallet balances, or when a
527
+ // user pastes it) — map it back to the 'native' sentinel so downstream native
528
+ // handling kicks in: gas-reserve deduction, approval skip, native-side quote.
529
+ const toNative = (addr) => (addr.toLowerCase() === PoolService_1.ZERO_ADDRESS ? 'native' : addr);
530
+ const key = directAddress ?? symbolHint ?? (refStr || undefined);
531
+ if (!key) {
532
+ this.dbg(`resolveSide:${label}`, { refStr, symbolStr, chain, resolved: 'none' });
533
+ return { address: null, symbol: symbolHint };
534
+ }
535
+ const lookup = await this.resolveContractAddress(key, chain, walletAddress);
536
+ this.dbg(`resolveSide:${label}`, { refStr, symbolStr, chain, key, lookup });
537
+ if (!lookup) {
538
+ // A concrete address with no enrichment — still usable.
539
+ if (directAddress)
540
+ return { address: toNative(directAddress), symbol: symbolHint };
541
+ return { address: null, symbol: symbolHint };
542
+ }
543
+ return { address: toNative(lookup.address), symbol: symbolHint ?? lookup.symbol, decimals: lookup.decimals };
544
+ }
545
+ // -------------------------------------------------------------------------
546
+ // Branch 1.75: build confirm tx (estimate + raw swap tx + approve)
547
+ // -------------------------------------------------------------------------
548
+ /**
549
+ * Pay token + dest token + a pay-token amount are known. Fetch a swap quote
550
+ * (estimated receive amount + raw swap tx), check approval, and return a
551
+ * `BuyTokenConfirmTx` UI payload the FE executes itself. Returns `null` when
552
+ * anything we need can't be produced (bad chain, quote failed, non-EVM tx).
553
+ */
554
+ async buildConfirmTx(input) {
555
+ const { userContext, chain, destContract, destSymbol, destDecimals, payAddr, paySymbol, payDecimals, payAmount } = input;
556
+ const walletAddress = userContext?.walletAddress;
557
+ if (!walletAddress || !chain || !destContract)
558
+ return null;
559
+ const meta = (0, PoolService_1.getChainMeta)(chain);
560
+ const numericChainId = Number.parseInt(chain, 16);
561
+ if (!meta || !Number.isFinite(numericChainId))
562
+ return null;
563
+ // Native pay token needs no decimals (chain meta) and never approves.
564
+ const payIsNative = payAddr === 'native';
565
+ const payDec = payIsNative ? (meta.native.decimals ?? 18) : payDecimals;
566
+ if (payDec === undefined)
567
+ return null;
568
+ const rawAmount = this.toRawAmount(payAmount, payDec);
569
+ if (!rawAmount)
570
+ return null;
571
+ // Providers want a concrete address for the native side; both accept zero.
572
+ const srcTokenAddress = payIsNative ? PoolService_1.ZERO_ADDRESS : payAddr;
573
+ const dstTokenAddress = destContract === 'native' ? PoolService_1.ZERO_ADDRESS : destContract;
574
+ let quote;
575
+ try {
576
+ const service = await swap_1.swapServiceFactory.getServiceByProvider('debridge');
577
+ quote = await service.getQuote({
578
+ srcChainId: numericChainId,
579
+ srcTokenAddress,
580
+ srcTokenAmount: rawAmount,
581
+ dstChainId: numericChainId,
582
+ dstTokenAddress,
583
+ recipientAddress: walletAddress,
584
+ senderAddress: walletAddress,
585
+ slippage: 'auto',
586
+ isCrossChain: false,
587
+ });
588
+ }
589
+ catch (err) {
590
+ // Network/throw — surface the message so the user sees the real reason.
591
+ return { quoteError: err instanceof Error ? err.message : String(err) };
592
+ }
593
+ // The provider returned a structured failure — forward its reason verbatim.
594
+ if (!quote.success) {
595
+ this.dbg('buildConfirmTx: quote failed', { error: quote.error, errorMessage: quote.errorMessage });
596
+ return { quoteError: this.extractQuoteError(quote) };
597
+ }
598
+ if (!quote.tx)
599
+ return { quoteError: 'No route available for this swap.' };
600
+ const tx = quote.tx;
601
+ if (!tx.to || typeof tx.data !== 'string')
602
+ return null; // non-EVM payload.
603
+ const swapTx = {
604
+ chainId: chain,
605
+ to: tx.to,
606
+ data: tx.data,
607
+ value: tx.value ?? '0',
608
+ from: walletAddress,
609
+ };
610
+ // Estimated receive amount of the dest token (provider-specific shape).
611
+ const estimatedOutRaw = this.extractOutRaw(quote);
612
+ const estimatedOut = estimatedOutRaw && destDecimals !== undefined
613
+ ? this.trimAmount(Number(estimatedOutRaw) / 10 ** destDecimals)
614
+ : undefined;
615
+ // Approval: native never approves; otherwise ask the provider.
616
+ let approveTx;
617
+ if (!payIsNative) {
618
+ approveTx = await this.buildApproveTx({ chain, walletAddress, payAddr, payDec, rawAmount, quote });
619
+ }
620
+ const props = {
621
+ chain: { hexId: chain, name: meta.name },
622
+ payToken: {
623
+ address: payIsNative ? 'native' : payAddr,
624
+ symbol: paySymbol,
625
+ decimals: payDec,
626
+ amount: payAmount,
627
+ rawAmount,
628
+ },
629
+ buyToken: {
630
+ address: destContract,
631
+ symbol: destSymbol,
632
+ decimals: destDecimals,
633
+ amount: estimatedOut,
634
+ rawAmount: estimatedOutRaw,
635
+ },
636
+ estimatedOut,
637
+ estimatedOutRaw,
638
+ provider: quote.provider,
639
+ swapTx,
640
+ approveTx,
641
+ };
642
+ const ui = { component: 'BuyTokenConfirmTx', props };
643
+ const estPart = estimatedOut ? ` They will receive about ${estimatedOut} ${destSymbol}.` : '';
644
+ return {
645
+ ui: ui,
646
+ _instructions: `A confirmation panel has been opened for buying ${destSymbol} with ${payAmount} ${paySymbol ?? 'the selected token'}.${estPart} ` +
647
+ 'Briefly tell the user, in their language, the amount they are spending and the estimated amount they will receive, ' +
648
+ 'and ask them to review and confirm to complete the purchase. The estimate is approximate — say "about"/"estimated", never a guaranteed amount. ' +
649
+ 'NEVER invent numbers not provided here. Do NOT mention tool names, UI, forms, or internal steps like approval.',
650
+ };
651
+ }
652
+ // -------------------------------------------------------------------------
653
+ // Branch 1.3: derive a pay amount from a buy-side amount via USD prices
654
+ // -------------------------------------------------------------------------
655
+ /**
656
+ * deBridge only quotes EXACT_INPUT, so a "buy X of the dest token" request has
657
+ * no input amount to seed the quote. Convert the desired output into a pay-token
658
+ * amount using each side's live USD price:
659
+ *
660
+ * payAmount ≈ (buyAmount × destPriceUsd) / payPriceUsd × (1 + buffer)
661
+ *
662
+ * The +2% buffer covers slippage + fees so the user receives AT LEAST roughly
663
+ * the requested amount (the confirm panel still shows the REAL estimate from
664
+ * the quote, not this derived figure). Returns `null` when either token's USD
665
+ * price can't be fetched — the caller then asks for a pay amount instead.
666
+ */
667
+ async derivePayAmountFromBuy(input) {
668
+ const { chain, buyAmount, destContract, payAddr } = input;
669
+ const [destPrice, payPrice] = await Promise.all([
670
+ this.getUsdPrice(destContract, chain),
671
+ this.getUsdPrice(payAddr, chain),
672
+ ]);
673
+ this.dbg('derivePayAmountFromBuy: prices', { destPrice, payPrice });
674
+ if (!destPrice || !payPrice)
675
+ return null;
676
+ const buyNum = Number(buyAmount);
677
+ if (!Number.isFinite(buyNum) || buyNum <= 0)
678
+ return null;
679
+ const BUFFER = 1.02; // +2% headroom for slippage + provider/protocol fees.
680
+ const rawPay = (buyNum * destPrice) / payPrice;
681
+ if (!Number.isFinite(rawPay) || rawPay <= 0)
682
+ return null;
683
+ return this.trimAmount(rawPay * BUFFER);
684
+ }
685
+ /**
686
+ * Best-effort live USD price for one side of the swap, sourced from
687
+ * Pantograph (via `MoralisService.getTokenMetadata`, which queries Pantograph
688
+ * first). The native coin (`'native'`) is looked up with the zero address —
689
+ * the Pantograph token endpoint returns the native row for it. Returns `null`
690
+ * when no positive price is available so the caller can degrade gracefully.
691
+ */
692
+ async getUsdPrice(tokenAddrOrNative, chain) {
693
+ if (!this.moralis)
694
+ return null;
695
+ const address = tokenAddrOrNative === 'native' ? PoolService_1.ZERO_ADDRESS : tokenAddrOrNative;
696
+ const res = await this.moralis.getTokenMetadata({ address, chain });
697
+ const price = res.success ? res.data?.usd_price : undefined;
698
+ return typeof price === 'number' && Number.isFinite(price) && price > 0 ? price : null;
699
+ }
700
+ // -------------------------------------------------------------------------
701
+ // Amount-spec resolution ("50%" / "5$" / plain) for the pay / buy sides
702
+ // -------------------------------------------------------------------------
703
+ /**
704
+ * Resolve the PAY-side amount, which may be plain ("10"), a PERCENT of the
705
+ * held pay-token balance ("50%"), or a USD value ("5$"). Percent reads the
706
+ * wallet's pay-token balance; usd divides by the pay token's USD price. Returns
707
+ * a plain token amount string, or `null` when nothing usable was given / the
708
+ * balance or price needed for a percent/usd couldn't be fetched (the caller's
709
+ * existing "needs amount" / amount-picker branches then take over).
710
+ */
711
+ async resolvePayAmountSpec(rawAmount, payAddr, chain, walletAddress, paySymbol) {
712
+ const spec = (0, amountSpec_1.parseAmountSpec)(rawAmount);
713
+ if (!spec)
714
+ return null;
715
+ if (spec.kind === 'token')
716
+ return this.trimAmount(spec.value);
717
+ if (spec.kind === 'percent') {
718
+ if (!walletAddress)
719
+ return null;
720
+ const bal = await this.readPayBalance(walletAddress, payAddr, chain);
721
+ this.dbg('resolvePayAmountSpec: percent', { percent: spec.percent, paySymbol, bal: bal?.balanceNum });
722
+ if (!bal || !Number.isFinite(bal.balanceNum) || bal.balanceNum <= 0)
723
+ return null;
724
+ // 100% ("max"/"all") spends the EXACT spendable balance string, never a
725
+ // re-rounded number — `trimAmount` can round UP, which would then trip the
726
+ // balance check (a second gas-reserve read can land a hair lower). The
727
+ // exact string is the gas-aware spendable for native (readPayBalance) or
728
+ // the held balance for an ERC-20.
729
+ if (spec.percent >= 100)
730
+ return this.cleanAmountString(bal.balanceFormatted);
731
+ return this.trimAmount((bal.balanceNum * spec.percent) / 100);
732
+ }
733
+ // usd
734
+ const price = await this.getUsdPrice(payAddr, chain);
735
+ this.dbg('resolvePayAmountSpec: usd', { usd: spec.usd, paySymbol, price });
736
+ if (!price)
737
+ return null;
738
+ return this.trimAmount(spec.usd / price);
739
+ }
740
+ /**
741
+ * Resolve the BUY-side amount, which may be plain ("5") or a USD value ("5$",
742
+ * "buy $5 of PEPE"). USD divides by the DEST token's price to get how many
743
+ * dest tokens "$X worth" is — that token figure then flows through Branch 1.3
744
+ * to derive the pay amount. A PERCENT on the buy side is meaningless (percent
745
+ * of what?), so it's ignored (returns null). Returns a plain token amount
746
+ * string or `null`.
747
+ */
748
+ async resolveBuyAmountSpec(rawAmount, destContract, chain, destSymbol) {
749
+ const spec = (0, amountSpec_1.parseAmountSpec)(rawAmount);
750
+ if (!spec)
751
+ return null;
752
+ if (spec.kind === 'token')
753
+ return this.trimAmount(spec.value);
754
+ if (spec.kind === 'percent')
755
+ return null; // not meaningful for the buy side.
756
+ // usd → dest-token quantity via the dest price.
757
+ if (!destContract)
758
+ return null;
759
+ const price = await this.getUsdPrice(destContract, chain);
760
+ this.dbg('resolveBuyAmountSpec: usd', { usd: spec.usd, destSymbol, price });
761
+ if (!price)
762
+ return null;
763
+ return this.trimAmount(spec.usd / price);
764
+ }
765
+ /**
766
+ * Check whether the pay token needs an allowance for this swap and, if so,
767
+ * return a ready-to-sign ERC-20 approve tx. Relay bundles the approve step in
768
+ * the quote (`approvalData` is the tx); deBridge only reports the spender
769
+ * (`tx.to`), so we encode the approve calldata ourselves.
770
+ */
771
+ async buildApproveTx(input) {
772
+ const { chain, walletAddress, payAddr, payDec, rawAmount, quote } = input;
773
+ try {
774
+ const service = await swap_1.swapServiceFactory.getServiceByProvider('debridge');
775
+ const approval = await service.checkApproval({
776
+ chain,
777
+ userAddress: walletAddress,
778
+ tokenAddress: payAddr,
779
+ amount: rawAmount,
780
+ tokenDecimals: payDec,
781
+ quoteData: quote,
782
+ });
783
+ if (!approval.isNeeded)
784
+ return undefined;
785
+ // Integrated providers (Relay) hand back the approve tx directly.
786
+ const data = (approval.approvalData ?? {});
787
+ if (data.to && typeof data.data === 'string') {
788
+ return { chainId: chain, to: data.to, data: data.data, value: data.value ?? '0', from: walletAddress };
789
+ }
790
+ // deBridge: we only know the spender (the swap router). Encode approve().
791
+ const spender = approval.contractAddress;
792
+ if (!spender)
793
+ return undefined;
794
+ const approveData = (0, web3_1.encodeFunctionData)({
795
+ abi: abi_1.erc20Abi,
796
+ functionName: 'approve',
797
+ args: [spender, BigInt(rawAmount)],
798
+ });
799
+ return { chainId: chain, to: payAddr, data: approveData, value: '0', from: walletAddress };
800
+ }
801
+ catch {
802
+ return undefined;
803
+ }
804
+ }
805
+ /** Human amount → smallest-unit decimal string (no float drift via BigInt). */
806
+ toRawAmount(human, decimals) {
807
+ const trimmed = human.trim();
808
+ if (!/^\d*\.?\d+$/.test(trimmed))
809
+ return null;
810
+ const [intPart, fracPartRaw = ''] = trimmed.split('.');
811
+ const fracPart = fracPartRaw.slice(0, decimals).padEnd(decimals, '0');
812
+ try {
813
+ const raw = BigInt(intPart || '0') * 10n ** BigInt(decimals) + BigInt(fracPart || '0');
814
+ return raw <= 0n ? null : raw.toString();
815
+ }
816
+ catch {
817
+ return null;
818
+ }
819
+ }
820
+ /**
821
+ * Pull a human-readable failure reason out of a failed quote. Adapters set
822
+ * `errorMessage` (string) and `error` (string or a provider object). deBridge
823
+ * also nests a reason in `raw.errorMessage` / `raw.error`. Falls back to a
824
+ * generic line so the caller always has something to show.
825
+ */
826
+ extractQuoteError(quote) {
827
+ if (quote.errorMessage && quote.errorMessage.trim())
828
+ return quote.errorMessage.trim();
829
+ const fromError = (e) => {
830
+ if (typeof e === 'string' && e.trim())
831
+ return e.trim();
832
+ if (e && typeof e === 'object') {
833
+ const o = e;
834
+ for (const v of [o.message, o.errorMessage, o.error]) {
835
+ if (typeof v === 'string' && v.trim())
836
+ return v.trim();
837
+ }
838
+ }
839
+ return undefined;
840
+ };
841
+ const raw = (quote.raw ?? {});
842
+ return (fromError(quote.error) ??
843
+ fromError(raw.errorMessage) ??
844
+ fromError(raw.error) ??
845
+ 'The swap could not be quoted right now.');
846
+ }
847
+ /**
848
+ * Estimated receive amount in the dest token's smallest units. Shape differs
849
+ * per provider — deBridge same-chain: `raw.tokenOut.minAmount`; deBridge
850
+ * cross-chain: `raw.estimation.dstChainTokenOut.amount`; Relay:
851
+ * `raw.details.currencyOut.{amount|minimumAmount}`.
852
+ */
853
+ extractOutRaw(quote) {
854
+ const raw = (quote.raw ?? {});
855
+ const candidate = raw.tokenOut?.amount ??
856
+ raw.tokenOut?.minAmount ??
857
+ raw.details?.currencyOut?.amount ??
858
+ raw.details?.currencyOut?.minimumAmount ??
859
+ raw.estimation?.dstChainTokenOut?.recommendedAmount ??
860
+ raw.estimation?.dstChainTokenOut?.amount;
861
+ return candidate && /^\d+$/.test(candidate) ? candidate : undefined;
862
+ }
863
+ // -------------------------------------------------------------------------
864
+ // Branch 1: pay-token picker
865
+ // -------------------------------------------------------------------------
866
+ async buildPayWithPicker(input) {
867
+ const { args, userContext, destSymbol, destContract } = input;
868
+ const chain = this.requireChain(args, userContext) ?? undefined;
869
+ const moralis = this.moralis;
870
+ const walletAddress = userContext.walletAddress;
871
+ const balancesRes = await moralis.getWalletTokenBalances({
872
+ address: walletAddress,
873
+ chain,
874
+ excludeSpam: true,
875
+ excludeUnverifiedContracts: true,
876
+ });
877
+ const balances = balancesRes.success ? (balancesRes.data?.result ?? []) : [];
878
+ // Include native + ERC-20s, but skip the dest token itself: paying with the
879
+ // token you're buying is a self-swap the provider rejects. Match by symbol OR
880
+ // address so a same-symbol holding at a different address is still excluded.
881
+ const destLower = destContract?.toLowerCase();
882
+ const destSymLower = destSymbol.trim().toLowerCase();
883
+ const holdings = balances
884
+ .filter((t) => {
885
+ if (!t.symbol)
886
+ return false;
887
+ if (destSymLower && t.symbol.toLowerCase() === destSymLower)
888
+ return false;
889
+ if (t.native_token)
890
+ return destLower !== 'native';
891
+ if (!t.token_address)
892
+ return false;
893
+ if (destLower && t.token_address.toLowerCase() === destLower)
894
+ return false;
895
+ return true;
896
+ })
897
+ .sort((a, b) => (b.usd_value ?? 0) - (a.usd_value ?? 0))
898
+ .slice(0, 8);
899
+ if (holdings.length === 0) {
900
+ return {
901
+ error: 'no_pay_token_holdings',
902
+ _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.',
903
+ };
904
+ }
905
+ // Prefer the caller's already-converted amounts (so "1$"/"50%" become a real
906
+ // token quantity in the click-prompt); fall back to re-parsing args.
907
+ const buyAmount = input.buyAmount ?? this.normaliseAmount(args.buy_amount);
908
+ const payAmount = input.payAmount ?? this.normaliseAmount(args.pay_with_amount);
909
+ // Click-prompt is re-submitted as a fresh user turn → phrase it in the
910
+ // user's language via the LLM-supplied "buy X with Y" template. The {token}
911
+ // is always the destination; {pay} is the chosen wallet token; {amount} is
912
+ // whichever side already has a value (buy amount rides with the dest token,
913
+ // pay amount rides with the pay token).
914
+ const buyWithTemplate = typeof args.buy_with_prompt_template === 'string' ? args.buy_with_prompt_template : '';
915
+ const actionButtons = holdings.map((t) => {
916
+ const sym = t.symbol;
917
+ const name = t.name?.trim() || sym;
918
+ const amount = t.balance_formatted ? `${this.cleanAmountString(t.balance_formatted)} ` : '';
919
+ const usdLabel = typeof t.usd_value === 'number' && t.usd_value > 0 ? ` ($${t.usd_value.toFixed(2)})` : '';
920
+ const label = `${name}: ${amount}${sym}${usdLabel}`;
921
+ // buy_amount sits next to the bought token ({buy_amount}); otherwise an
922
+ // optional pay_with_amount sits next to the pay token ({pay_amount}).
923
+ // Only one side ever carries an amount.
924
+ const prompt = buyAmount
925
+ ? this.buildBuyWithPrompt(buyWithTemplate, destSymbol, sym, buyAmount, null)
926
+ : this.buildBuyWithPrompt(buyWithTemplate, destSymbol, sym, null, payAmount);
927
+ return { label, prompt };
928
+ });
929
+ return {
930
+ actionButtons,
931
+ _instructions: `Reply briefly in the user's language: ask them to pick which token they want to spend to buy ${destSymbol} from the options below. Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms.`,
932
+ };
933
+ }
934
+ // -------------------------------------------------------------------------
935
+ // Branch 1.4: balance check (pay token named — with or without an amount)
936
+ // -------------------------------------------------------------------------
937
+ /**
938
+ * Guard the buy flow against a pay token the user can't fund. Returns:
939
+ * - `null` when the user holds enough (or no amount yet, or balances can't be read).
940
+ * - `no_pay_balance` (+ picker buttons) when the wallet holds none of it.
941
+ * - `insufficient_pay_balance` (+ percentage buttons) when it holds some but
942
+ * less than the requested amount.
943
+ */
944
+ async checkPayBalance(input) {
945
+ const { args, userContext, chain, destSymbol, destContract, payAddr, payAmount, buyAmount, derivedFromBuy } = input;
946
+ const walletAddress = userContext.walletAddress;
947
+ const bal = await this.readPayBalance(walletAddress, payAddr, chain);
948
+ const paySymbol = input.paySymbol ?? bal?.symbol ?? 'the selected token';
949
+ // No holding at all: tell the user, then offer the tokens they DO hold.
950
+ if (!bal) {
951
+ this.dbg('checkPayBalance: NOT held → pay picker fallback', { paySymbol });
952
+ const picker = await this.buildPayWithPicker({
953
+ args,
954
+ userContext,
955
+ destSymbol,
956
+ destContract,
957
+ buyAmount,
958
+ payAmount,
959
+ });
960
+ if ('actionButtons' in picker) {
961
+ return {
962
+ error: 'no_pay_balance',
963
+ actionButtons: picker.actionButtons,
964
+ _instructions: `The user wanted to spend ${paySymbol} to buy ${destSymbol}, but their wallet holds no ${paySymbol} on this chain. ` +
965
+ `Tell them, in their language, that they don't have any ${paySymbol}, then say they can instead choose one of the tokens they already hold (shown below) to pay with. ` +
966
+ 'Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.',
967
+ };
968
+ }
969
+ return picker;
970
+ }
971
+ // No amount yet — the holding exists, so let the amount picker take over.
972
+ if (!payAmount)
973
+ return null;
974
+ // When the amount was SIZED OFF the balance (a percent / "max" / usd spec),
975
+ // it's already ≤ spendable by construction — skip the over-balance check.
976
+ // Re-checking would compare it against a freshly re-read spendable, which can
977
+ // drift a hair (the gas reserve is read from a separate RPC call), and a
978
+ // legitimate "max" would falsely trip as "insufficient". Only a plain
979
+ // user-typed amount ("buy USDC with 5 ETH") is genuinely checked.
980
+ if (input.amountSizedToBalance)
981
+ return null;
982
+ const wanted = Number(payAmount);
983
+ if (!Number.isFinite(wanted) || wanted <= bal.balanceNum)
984
+ return null;
985
+ // Holds some but not enough: report the shortfall + percentage options.
986
+ const spendable = this.trimAmount(bal.balanceNum);
987
+ const actionButtons = this.percentSpendButtons({
988
+ balanceFormatted: bal.balanceFormatted,
989
+ balanceNum: bal.balanceNum,
990
+ paySymbol,
991
+ destSymbol,
992
+ buyWithTemplate: typeof args.buy_with_prompt_template === 'string' ? args.buy_with_prompt_template : '',
993
+ });
994
+ // Frame the message by what the user actually said: a derived pay amount
995
+ // came from "buy X dest", so explain the conversion; otherwise they typed
996
+ // the pay amount directly.
997
+ const needLine = derivedFromBuy
998
+ ? `Buying ${derivedFromBuy} ${destSymbol} needs about ${payAmount} ${paySymbol}, but they only have ${spendable} ${paySymbol} on this chain — not enough. ` +
999
+ `Tell them, in their language, that buying ${derivedFromBuy} ${destSymbol} would cost about ${payAmount} ${paySymbol}, which is more than their balance of ${spendable} ${paySymbol}, and ask them to buy a smaller amount or spend less — `
1000
+ : `The user wants to spend ${payAmount} ${paySymbol} to buy ${destSymbol}, but they only have ${spendable} ${paySymbol} on this chain — not enough. ` +
1001
+ `Tell them, in their language, that ${payAmount} ${paySymbol} exceeds their balance of ${spendable} ${paySymbol}, and ask them to pick a smaller amount — `;
1002
+ return {
1003
+ error: 'insufficient_pay_balance',
1004
+ actionButtons,
1005
+ _instructions: needLine +
1006
+ 'invite them to choose one of the options below (sized to their balance) or type the amount they want. ' +
1007
+ 'CRITICAL: clickable percentage buttons are rendered separately below your message — your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). ' +
1008
+ 'Do NOT invent numbers, and do NOT mention tool names, UI, buttons, or forms.',
1009
+ };
1010
+ }
1011
+ /**
1012
+ * Read the connected wallet's balance of the pay token. Returns the held
1013
+ * amount + on-chain symbol, or `null` when balances can't be fetched / the
1014
+ * token isn't held (zero balance or not held at all).
1015
+ *
1016
+ * For the NATIVE pay coin the returned balance is the SPENDABLE figure — the
1017
+ * held balance minus a silently-estimated swap gas reserve (gasLimit
1018
+ * 1_000_000 × gasPrice) — so the percent buttons, amount picker, and balance
1019
+ * check stay within what the user can actually spend while still covering gas.
1020
+ * The reserve is never surfaced; callers only ever expose the spendable
1021
+ * number. ERC-20 pay tokens are returned at their raw held balance (gas is
1022
+ * paid in the native coin, not the pay token).
1023
+ */
1024
+ async readPayBalance(walletAddress, payAddr, chain) {
1025
+ if (!this.moralis)
1026
+ return null;
1027
+ const balancesRes = await this.moralis.getWalletTokenBalances({
1028
+ address: walletAddress,
1029
+ chain,
1030
+ excludeSpam: true,
1031
+ excludeUnverifiedContracts: true,
1032
+ });
1033
+ const balances = balancesRes.success ? (balancesRes.data?.result ?? []) : [];
1034
+ this.dbg('readPayBalance', {
1035
+ payAddr,
1036
+ chain,
1037
+ ok: balancesRes.success,
1038
+ count: balances.length,
1039
+ held: balances.map((t) => ({ sym: t.symbol, addr: t.token_address, bal: t.balance_formatted })),
1040
+ });
1041
+ const payLower = payAddr.toLowerCase();
1042
+ const payBal = balances.find((t) => payLower === 'native' ? t.native_token === true : t.token_address?.toLowerCase() === payLower);
1043
+ if (!payBal?.balance_formatted) {
1044
+ this.dbg('readPayBalance: pay token NOT held → null', { payAddr });
1045
+ return null;
1046
+ }
1047
+ const balanceNum = Number(payBal.balance_formatted);
1048
+ if (!Number.isFinite(balanceNum) || balanceNum <= 0) {
1049
+ this.dbg('readPayBalance: zero/invalid balance → null', { payAddr, balance: payBal.balance_formatted });
1050
+ return null;
1051
+ }
1052
+ // Native pay coin: reduce to the spendable balance so we never quote/size a
1053
+ // buy that would leave the user unable to pay gas.
1054
+ if (payLower === 'native' && chain) {
1055
+ const reserve = await (0, gasReserve_1.estimateNativeGasReserveWei)(chain, 'swap');
1056
+ const spendable = (0, gasReserve_1.spendableNative)(balanceNum, reserve, chain);
1057
+ this.dbg('readPayBalance: native spendable', { balanceNum, spendable });
1058
+ if (spendable <= 0) {
1059
+ this.dbg('readPayBalance: native spendable ≤ 0 → null', { balanceNum });
1060
+ return null;
1061
+ }
1062
+ const spendableStr = this.trimAmount(spendable);
1063
+ return { balanceFormatted: spendableStr, balanceNum: spendable, symbol: payBal.symbol };
1064
+ }
1065
+ this.dbg('readPayBalance: held', { payAddr, balance: payBal.balance_formatted, symbol: payBal.symbol });
1066
+ return { balanceFormatted: payBal.balance_formatted, balanceNum, symbol: payBal.symbol };
1067
+ }
1068
+ /**
1069
+ * 25/50/75/100% quick-spend buttons sized off a held balance. 100% spends the
1070
+ * EXACT held string; the partial fractions are float-trimmed. The amount rides
1071
+ * in the prompt; the label is a bare percent.
1072
+ */
1073
+ percentSpendButtons(input) {
1074
+ const { balanceFormatted, balanceNum, paySymbol, destSymbol, buyWithTemplate } = input;
1075
+ const fractions = [0.25, 0.5, 0.75, 1];
1076
+ return fractions.map((f) => {
1077
+ const amount = f === 1 ? this.cleanAmountString(balanceFormatted) : this.trimAmount(balanceNum * f);
1078
+ // The percentage amount is the PAY amount → it rides with the pay token
1079
+ // ({pay_amount}), never with the bought token.
1080
+ return {
1081
+ label: `${Math.round(f * 100)}%`,
1082
+ prompt: this.buildBuyWithPrompt(buyWithTemplate, destSymbol, paySymbol, null, amount),
1083
+ };
1084
+ });
1085
+ }
1086
+ // -------------------------------------------------------------------------
1087
+ // Branch 1.5: amount picker (dest + pay known, no amount given)
1088
+ // -------------------------------------------------------------------------
1089
+ async buildAmountPicker(input) {
1090
+ const { userContext, chain, destSymbol, payAddr, buyWithTemplate } = input;
1091
+ const walletAddress = userContext.walletAddress;
1092
+ const bal = await this.readPayBalance(walletAddress, payAddr, chain);
1093
+ const paySymbol = input.paySymbol ?? bal?.symbol;
1094
+ if (!bal || !paySymbol)
1095
+ return null;
1096
+ const actionButtons = this.percentSpendButtons({
1097
+ balanceFormatted: bal.balanceFormatted,
1098
+ balanceNum: bal.balanceNum,
1099
+ paySymbol,
1100
+ destSymbol,
1101
+ buyWithTemplate,
1102
+ });
1103
+ const spendable = this.trimAmount(bal.balanceNum);
1104
+ return {
1105
+ actionButtons,
1106
+ _instructions: `The user's spendable balance is ${spendable} ${paySymbol}. Reply briefly in the user's language: tell them their spendable ${paySymbol} balance and ask how much they want to spend to buy ${destSymbol}, 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 — 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.`,
1107
+ };
1108
+ }
1109
+ /**
1110
+ * Trim IEEE-754 noise from a computed amount (≤8 significant digits) and
1111
+ * ALWAYS return plain decimal notation — never exponential ("1.5e-7").
1112
+ */
1113
+ trimAmount(value) {
1114
+ if (!Number.isFinite(value))
1115
+ return '0';
1116
+ return this.toPlainDecimal(parseFloat(value.toPrecision(8)));
1117
+ }
1118
+ /** Format a number as a plain decimal string, expanding any e-notation. */
1119
+ toPlainDecimal(n) {
1120
+ const s = String(n);
1121
+ if (!/e/i.test(s))
1122
+ return s;
1123
+ const neg = n < 0;
1124
+ const [mantissa, expPart] = Math.abs(n).toString().split('e');
1125
+ const exp = Number(expPart);
1126
+ const [intPart, fracPart = ''] = mantissa.split('.');
1127
+ const digits = intPart + fracPart;
1128
+ let out;
1129
+ if (exp >= 0) {
1130
+ const pointPos = intPart.length + exp;
1131
+ out =
1132
+ pointPos >= digits.length
1133
+ ? digits.padEnd(pointPos, '0')
1134
+ : `${digits.slice(0, pointPos)}.${digits.slice(pointPos)}`;
1135
+ }
1136
+ else {
1137
+ out = `0.${'0'.repeat(-exp - intPart.length)}${digits}`;
1138
+ }
1139
+ if (out.includes('.'))
1140
+ out = out.replace(/\.?0+$/, '');
1141
+ return neg ? `-${out}` : out;
1142
+ }
1143
+ /**
1144
+ * Sanitize an amount STRING from an external source (e.g. Moralis
1145
+ * `balance_formatted`): expand e-notation to plain decimal; else pass through.
1146
+ */
1147
+ cleanAmountString(s) {
1148
+ if (!/e/i.test(s))
1149
+ return s;
1150
+ const n = Number(s);
1151
+ return Number.isFinite(n) ? this.toPlainDecimal(n) : s;
1152
+ }
1153
+ /**
1154
+ * Build the trending-picker click-prompt ("buy {token}") from the
1155
+ * LLM-supplied, already-localized template. The click-prompt is re-submitted
1156
+ * as a fresh user turn, so keeping it in the user's language keeps that turn
1157
+ * in the same language. Falls back to English when the LLM omits a template.
1158
+ */
1159
+ buildBuyPrompt(template, token) {
1160
+ const tpl = template && template.includes('{token}') ? template : 'buy {token}';
1161
+ return tpl
1162
+ .replace(/\{token\}/g, token)
1163
+ .replace(/\s+/g, ' ')
1164
+ .trim();
1165
+ }
1166
+ /**
1167
+ * Build the "buy X with Y" click-prompt from the LLM-supplied, already-
1168
+ * localized template by substituting placeholders:
1169
+ * {token} → token bought {pay} → payment token
1170
+ * {buy_amount} → quantity bought (next to {token})
1171
+ * {pay_amount} → quantity paid (next to {pay})
1172
+ * The localized verb and "with" preposition live INSIDE the template, so we
1173
+ * only substitute concrete values. Only one side ever carries an amount; the
1174
+ * unused amount placeholder is replaced with '' (no preposition is attached
1175
+ * to it, so nothing dangles). Falls back to an English template when the LLM
1176
+ * omits one.
1177
+ */
1178
+ buildBuyWithPrompt(template, token, pay, buyAmount, payAmount) {
1179
+ const tpl = template && template.includes('{token}') && template.includes('{pay}')
1180
+ ? template
1181
+ : 'buy {buy_amount} {token} with {pay_amount} {pay}';
1182
+ return tpl
1183
+ .replace(/\{token\}/g, token)
1184
+ .replace(/\{pay\}/g, pay)
1185
+ .replace(/\{buy_amount\}/g, buyAmount ?? '')
1186
+ .replace(/\{pay_amount\}/g, payAmount ?? '')
1187
+ .replace(/\s+/g, ' ')
1188
+ .trim();
1189
+ }
1190
+ // -------------------------------------------------------------------------
1191
+ // Branch 0: trending picker (user wants to buy but named no token)
1192
+ // -------------------------------------------------------------------------
1193
+ async buildTrendingPicker(args, userContext) {
1194
+ if (!this.pantograph) {
1195
+ return {
1196
+ error: 'trending_unavailable',
1197
+ _instructions: 'Trending tokens are unavailable in this build (service not configured). Tell the user briefly and ask them to name a token to buy.',
1198
+ };
1199
+ }
1200
+ const chain = this.requireChain(args, userContext) ?? undefined;
1201
+ const requestedLimit = typeof args.limit === 'number' && Number.isFinite(args.limit) ? Math.floor(args.limit) : 6;
1202
+ const limit = Math.max(1, Math.min(10, requestedLimit));
1203
+ const res = await this.pantograph.getTrendingTokens({ chain, limit });
1204
+ const list = res.success ? (res.data ?? []) : [];
1205
+ if (list.length === 0) {
1206
+ return {
1207
+ error: 'no_trending_tokens',
1208
+ _instructions: 'No trending tokens are available for this chain right now. Tell the user briefly and suggest they name a token directly.',
1209
+ };
1210
+ }
1211
+ const tokens = list.filter((t) => t.symbol);
1212
+ // Click-prompt is re-submitted as a fresh user turn → phrase it in the
1213
+ // user's language using the LLM-supplied localized template.
1214
+ const buyTemplate = typeof args.buy_prompt_template === 'string' ? args.buy_prompt_template : '';
1215
+ const actionButtons = tokens.map((t) => {
1216
+ const sym = t.symbol;
1217
+ return { label: sym, prompt: this.buildBuyPrompt(buyTemplate, sym) };
1218
+ });
1219
+ const lines = tokens.map((t, i) => {
1220
+ const name = t.name?.trim() || t.symbol;
1221
+ const sym = t.symbol;
1222
+ const priceLabel = typeof t.usdPrice === 'number' && t.usdPrice > 0 ? `$${this.formatPrice(t.usdPrice)}` : 'N/A';
1223
+ const change = t.pricePercentChange?.['24h'];
1224
+ const hasChange = typeof change === 'number' && Number.isFinite(change);
1225
+ const arrow = hasChange ? (change > 0 ? '▲' : change < 0 ? '▼' : '▪') : '';
1226
+ const changeLabel = hasChange ? ` (${arrow} ${this.formatPercent(change)}%)` : '';
1227
+ return `${i + 1}. ${name} (${sym})\n${priceLabel}${changeLabel}`;
1228
+ });
1229
+ const listText = lines.join('\n-----\n') + `\n---\nIs there any token you want to buy?`;
1230
+ return {
1231
+ actionButtons,
1232
+ _instructions: `The trending tokens are on chain id "${chain ?? 'the connected chain'}" — use the human-readable chain name. ` +
1233
+ `Reply in the user's language. Start with a one-line header like "📈 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:\n\n${listText}`,
1234
+ };
1235
+ }
1236
+ /**
1237
+ * Format a 24h price-change percentage: ~6 significant digits, no trailing
1238
+ * noise, and always plain decimal (never exponential for a tiny change).
1239
+ */
1240
+ formatPercent(value) {
1241
+ if (!Number.isFinite(value))
1242
+ return '0';
1243
+ return this.toPlainDecimal(parseFloat(value.toPrecision(6)));
1244
+ }
1245
+ /** Compact price string: e.g. 0.00001234 → "0.00001234", 1234.5 → "1,234.50". */
1246
+ formatPrice(price) {
1247
+ if (price >= 1)
1248
+ return price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
1249
+ if (price >= 0.01)
1250
+ return price.toFixed(4);
1251
+ const str = price.toFixed(12);
1252
+ const match = str.match(/^0\.0*([1-9]\d{0,3})/);
1253
+ return match ? str.slice(0, (match[0].length || 0) + 0) : str;
1254
+ }
1255
+ }
1256
+ exports.BuyTokenTool = BuyTokenTool;
1257
+ //# sourceMappingURL=BuyTokenTool.js.map