amped-defi 1.0.0

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 (189) hide show
  1. package/README.md +757 -0
  2. package/dist/__mocks__/@sodax/sdk.d.ts +24 -0
  3. package/dist/__mocks__/@sodax/sdk.d.ts.map +1 -0
  4. package/dist/__mocks__/@sodax/sdk.js +24 -0
  5. package/dist/__mocks__/@sodax/sdk.js.map +1 -0
  6. package/dist/__tests__/setup.d.ts +4 -0
  7. package/dist/__tests__/setup.d.ts.map +1 -0
  8. package/dist/__tests__/setup.js +32 -0
  9. package/dist/__tests__/setup.js.map +1 -0
  10. package/dist/index.d.ts +66 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +281 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/policy/policyEngine.d.ts +119 -0
  15. package/dist/policy/policyEngine.d.ts.map +1 -0
  16. package/dist/policy/policyEngine.js +322 -0
  17. package/dist/policy/policyEngine.js.map +1 -0
  18. package/dist/providers/spokeProviderFactory.d.ts +38 -0
  19. package/dist/providers/spokeProviderFactory.d.ts.map +1 -0
  20. package/dist/providers/spokeProviderFactory.js +212 -0
  21. package/dist/providers/spokeProviderFactory.js.map +1 -0
  22. package/dist/sodax/client.d.ts +34 -0
  23. package/dist/sodax/client.d.ts.map +1 -0
  24. package/dist/sodax/client.js +99 -0
  25. package/dist/sodax/client.js.map +1 -0
  26. package/dist/tools/bridge.d.ts +105 -0
  27. package/dist/tools/bridge.d.ts.map +1 -0
  28. package/dist/tools/bridge.js +334 -0
  29. package/dist/tools/bridge.js.map +1 -0
  30. package/dist/tools/discovery.d.ts +141 -0
  31. package/dist/tools/discovery.d.ts.map +1 -0
  32. package/dist/tools/discovery.js +777 -0
  33. package/dist/tools/discovery.js.map +1 -0
  34. package/dist/tools/moneyMarket.d.ts +227 -0
  35. package/dist/tools/moneyMarket.d.ts.map +1 -0
  36. package/dist/tools/moneyMarket.js +867 -0
  37. package/dist/tools/moneyMarket.js.map +1 -0
  38. package/dist/tools/portfolio.d.ts +43 -0
  39. package/dist/tools/portfolio.d.ts.map +1 -0
  40. package/dist/tools/portfolio.js +538 -0
  41. package/dist/tools/portfolio.js.map +1 -0
  42. package/dist/tools/swap.d.ts +71 -0
  43. package/dist/tools/swap.d.ts.map +1 -0
  44. package/dist/tools/swap.js +762 -0
  45. package/dist/tools/swap.js.map +1 -0
  46. package/dist/tools/walletManagement.d.ts +80 -0
  47. package/dist/tools/walletManagement.d.ts.map +1 -0
  48. package/dist/tools/walletManagement.js +289 -0
  49. package/dist/tools/walletManagement.js.map +1 -0
  50. package/dist/types.d.ts +205 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +5 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/utils/errorUtils.d.ts +2 -0
  55. package/dist/utils/errorUtils.d.ts.map +1 -0
  56. package/dist/utils/errorUtils.js +19 -0
  57. package/dist/utils/errorUtils.js.map +1 -0
  58. package/dist/utils/errors.d.ts +144 -0
  59. package/dist/utils/errors.d.ts.map +1 -0
  60. package/dist/utils/errors.js +310 -0
  61. package/dist/utils/errors.js.map +1 -0
  62. package/dist/utils/positionAggregator.d.ts +122 -0
  63. package/dist/utils/positionAggregator.d.ts.map +1 -0
  64. package/dist/utils/positionAggregator.js +377 -0
  65. package/dist/utils/positionAggregator.js.map +1 -0
  66. package/dist/utils/priceService.d.ts +45 -0
  67. package/dist/utils/priceService.d.ts.map +1 -0
  68. package/dist/utils/priceService.js +108 -0
  69. package/dist/utils/priceService.js.map +1 -0
  70. package/dist/utils/sodaxApi.d.ts +92 -0
  71. package/dist/utils/sodaxApi.d.ts.map +1 -0
  72. package/dist/utils/sodaxApi.js +143 -0
  73. package/dist/utils/sodaxApi.js.map +1 -0
  74. package/dist/utils/tokenResolver.d.ts +54 -0
  75. package/dist/utils/tokenResolver.d.ts.map +1 -0
  76. package/dist/utils/tokenResolver.js +252 -0
  77. package/dist/utils/tokenResolver.js.map +1 -0
  78. package/dist/wallet/backendConfig.d.ts +37 -0
  79. package/dist/wallet/backendConfig.d.ts.map +1 -0
  80. package/dist/wallet/backendConfig.js +125 -0
  81. package/dist/wallet/backendConfig.js.map +1 -0
  82. package/dist/wallet/backends/BankrBackend.d.ts +73 -0
  83. package/dist/wallet/backends/BankrBackend.d.ts.map +1 -0
  84. package/dist/wallet/backends/BankrBackend.js +315 -0
  85. package/dist/wallet/backends/BankrBackend.js.map +1 -0
  86. package/dist/wallet/backends/BankrWalletProvider.d.ts +75 -0
  87. package/dist/wallet/backends/BankrWalletProvider.d.ts.map +1 -0
  88. package/dist/wallet/backends/BankrWalletProvider.js +243 -0
  89. package/dist/wallet/backends/BankrWalletProvider.js.map +1 -0
  90. package/dist/wallet/backends/EnvBackend.d.ts +50 -0
  91. package/dist/wallet/backends/EnvBackend.d.ts.map +1 -0
  92. package/dist/wallet/backends/EnvBackend.js +114 -0
  93. package/dist/wallet/backends/EnvBackend.js.map +1 -0
  94. package/dist/wallet/backends/EvmWalletSkillBackend.d.ts +40 -0
  95. package/dist/wallet/backends/EvmWalletSkillBackend.d.ts.map +1 -0
  96. package/dist/wallet/backends/EvmWalletSkillBackend.js +81 -0
  97. package/dist/wallet/backends/EvmWalletSkillBackend.js.map +1 -0
  98. package/dist/wallet/backends/index.d.ts +10 -0
  99. package/dist/wallet/backends/index.d.ts.map +1 -0
  100. package/dist/wallet/backends/index.js +10 -0
  101. package/dist/wallet/backends/index.js.map +1 -0
  102. package/dist/wallet/index.d.ts +9 -0
  103. package/dist/wallet/index.d.ts.map +1 -0
  104. package/dist/wallet/index.js +12 -0
  105. package/dist/wallet/index.js.map +1 -0
  106. package/dist/wallet/providers/AmpedWalletProvider.d.ts +107 -0
  107. package/dist/wallet/providers/AmpedWalletProvider.d.ts.map +1 -0
  108. package/dist/wallet/providers/AmpedWalletProvider.js +208 -0
  109. package/dist/wallet/providers/AmpedWalletProvider.js.map +1 -0
  110. package/dist/wallet/providers/BankrBackend.d.ts +105 -0
  111. package/dist/wallet/providers/BankrBackend.d.ts.map +1 -0
  112. package/dist/wallet/providers/BankrBackend.js +327 -0
  113. package/dist/wallet/providers/BankrBackend.js.map +1 -0
  114. package/dist/wallet/providers/LocalKeyBackend.d.ts +62 -0
  115. package/dist/wallet/providers/LocalKeyBackend.d.ts.map +1 -0
  116. package/dist/wallet/providers/LocalKeyBackend.js +152 -0
  117. package/dist/wallet/providers/LocalKeyBackend.js.map +1 -0
  118. package/dist/wallet/providers/chainConfig.d.ts +209 -0
  119. package/dist/wallet/providers/chainConfig.d.ts.map +1 -0
  120. package/dist/wallet/providers/chainConfig.js +175 -0
  121. package/dist/wallet/providers/chainConfig.js.map +1 -0
  122. package/dist/wallet/providers/index.d.ts +30 -0
  123. package/dist/wallet/providers/index.d.ts.map +1 -0
  124. package/dist/wallet/providers/index.js +32 -0
  125. package/dist/wallet/providers/index.js.map +1 -0
  126. package/dist/wallet/providers/types.d.ts +156 -0
  127. package/dist/wallet/providers/types.d.ts.map +1 -0
  128. package/dist/wallet/providers/types.js +11 -0
  129. package/dist/wallet/providers/types.js.map +1 -0
  130. package/dist/wallet/skillWalletAdapter.d.ts +96 -0
  131. package/dist/wallet/skillWalletAdapter.d.ts.map +1 -0
  132. package/dist/wallet/skillWalletAdapter.js +280 -0
  133. package/dist/wallet/skillWalletAdapter.js.map +1 -0
  134. package/dist/wallet/types.d.ts +134 -0
  135. package/dist/wallet/types.d.ts.map +1 -0
  136. package/dist/wallet/types.js +138 -0
  137. package/dist/wallet/types.js.map +1 -0
  138. package/dist/wallet/walletManager.d.ts +111 -0
  139. package/dist/wallet/walletManager.d.ts.map +1 -0
  140. package/dist/wallet/walletManager.js +476 -0
  141. package/dist/wallet/walletManager.js.map +1 -0
  142. package/dist/wallet/walletRegistry.d.ts +95 -0
  143. package/dist/wallet/walletRegistry.d.ts.map +1 -0
  144. package/dist/wallet/walletRegistry.js +184 -0
  145. package/dist/wallet/walletRegistry.js.map +1 -0
  146. package/index.js +2 -0
  147. package/openclaw.plugin.json +37 -0
  148. package/package.json +69 -0
  149. package/src/__mocks__/@sodax/sdk.ts +28 -0
  150. package/src/__tests__/errors.test.ts +238 -0
  151. package/src/__tests__/policyEngine.test.ts +354 -0
  152. package/src/__tests__/positionAggregator.test.ts +271 -0
  153. package/src/__tests__/setup.ts +35 -0
  154. package/src/__tests__/sodaxApi.test.ts +203 -0
  155. package/src/__tests__/walletRegistry.test.ts +155 -0
  156. package/src/index.ts +376 -0
  157. package/src/policy/policyEngine.ts +389 -0
  158. package/src/providers/spokeProviderFactory.ts +283 -0
  159. package/src/sodax/client.ts +113 -0
  160. package/src/tools/bridge.ts +425 -0
  161. package/src/tools/discovery.ts +989 -0
  162. package/src/tools/moneyMarket.ts +1265 -0
  163. package/src/tools/portfolio.ts +697 -0
  164. package/src/tools/swap.ts +926 -0
  165. package/src/tools/walletManagement.ts +359 -0
  166. package/src/types.ts +228 -0
  167. package/src/utils/errorUtils.ts +16 -0
  168. package/src/utils/errors.ts +396 -0
  169. package/src/utils/positionAggregator.ts +559 -0
  170. package/src/utils/priceService.ts +153 -0
  171. package/src/utils/sodaxApi.ts +261 -0
  172. package/src/utils/tokenResolver.ts +286 -0
  173. package/src/wallet/backendConfig.ts +151 -0
  174. package/src/wallet/backends/BankrBackend.ts +399 -0
  175. package/src/wallet/backends/BankrWalletProvider.ts +329 -0
  176. package/src/wallet/backends/EnvBackend.ts +149 -0
  177. package/src/wallet/backends/EvmWalletSkillBackend.ts +110 -0
  178. package/src/wallet/backends/index.ts +10 -0
  179. package/src/wallet/index.ts +14 -0
  180. package/src/wallet/providers/AmpedWalletProvider.ts +267 -0
  181. package/src/wallet/providers/BankrBackend.ts +407 -0
  182. package/src/wallet/providers/LocalKeyBackend.ts +184 -0
  183. package/src/wallet/providers/chainConfig.ts +194 -0
  184. package/src/wallet/providers/index.ts +62 -0
  185. package/src/wallet/providers/types.ts +186 -0
  186. package/src/wallet/skillWalletAdapter.ts +335 -0
  187. package/src/wallet/types.ts +248 -0
  188. package/src/wallet/walletManager.ts +561 -0
  189. package/src/wallet/walletRegistry.ts +216 -0
@@ -0,0 +1,989 @@
1
+ /**
2
+ * Discovery/Read Tools for Amped DeFi Plugin
3
+ *
4
+ * These tools provide read-only access to:
5
+ * - Supported chains and tokens
6
+ * - Wallet address resolution
7
+ * - Money market positions and reserves
8
+ *
9
+ * @module tools/discovery
10
+ */
11
+
12
+ import { Type, Static } from '@sinclair/typebox';
13
+ import { getSodaxClient } from '../sodax/client';
14
+ import { toSodaxChainId } from '../wallet/types';
15
+ import { getSpokeProvider } from '../providers/spokeProviderFactory';
16
+ import { getWalletManager, type IWalletBackend, type WalletInfo } from '../wallet';
17
+ import {
18
+ aggregateCrossChainPositions,
19
+ formatHealthFactor,
20
+ getHealthFactorStatus,
21
+ getPositionRecommendation
22
+ } from '../utils/positionAggregator';
23
+ import { getSodaxApiClient } from '../utils/sodaxApi';
24
+
25
+ // ============================================================================
26
+ // TypeBox Schemas
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Schema for amped_supported_chains - no parameters required
31
+ */
32
+ const SupportedChainsSchema = Type.Object({});
33
+
34
+ /**
35
+ * Schema for amped_supported_tokens
36
+ */
37
+ const SupportedTokensSchema = Type.Object({
38
+ module: Type.Union([
39
+ Type.Literal('swaps'),
40
+ Type.Literal('bridge'),
41
+ Type.Literal('moneyMarket'),
42
+ ]),
43
+ chainId: Type.String({
44
+ description: 'Spoke chain ID (e.g., "ethereum", "arbitrum", "sonic")',
45
+ }),
46
+ });
47
+
48
+ /**
49
+ * Schema for amped_wallet_address
50
+ */
51
+ const WalletAddressSchema = Type.Object({
52
+ walletId: Type.String({
53
+ description: 'Unique identifier for the wallet',
54
+ }),
55
+ });
56
+
57
+ /**
58
+ * Schema for amped_money_market_positions
59
+ */
60
+ const MoneyMarketPositionsSchema = Type.Object({
61
+ walletId: Type.String({
62
+ description: 'Unique identifier for the wallet',
63
+ }),
64
+ chainId: Type.String({
65
+ description: 'Spoke chain ID to query positions on',
66
+ }),
67
+ });
68
+
69
+ /**
70
+ * Schema for amped_money_market_reserves
71
+ */
72
+ const MoneyMarketReservesSchema = Type.Object({
73
+ chainId: Type.Optional(
74
+ Type.String({
75
+ description:
76
+ 'Optional chain ID. Money market is hub-centric, so this filters results for a specific spoke chain if needed',
77
+ })
78
+ ),
79
+ });
80
+
81
+ /**
82
+ * Schema for amped_cross_chain_positions
83
+ * Get aggregated positions view across all chains
84
+ */
85
+ const CrossChainPositionsSchema = Type.Object({
86
+ walletId: Type.String({
87
+ description: 'Unique identifier for the wallet',
88
+ }),
89
+ chainIds: Type.Optional(
90
+ Type.Array(Type.String(), {
91
+ description: 'Optional array of specific chain IDs to query (defaults to all supported chains)',
92
+ })
93
+ ),
94
+ includeZeroBalances: Type.Optional(
95
+ Type.Boolean({
96
+ description: 'Include positions with zero balance',
97
+ default: false,
98
+ })
99
+ ),
100
+ minUsdValue: Type.Optional(
101
+ Type.Number({
102
+ description: 'Minimum USD value threshold for including positions',
103
+ default: 0.01,
104
+ })
105
+ ),
106
+ });
107
+
108
+ /**
109
+ * Schema for amped_user_intents
110
+ * Query user intent history from SODAX API
111
+ */
112
+ const UserIntentsSchema = Type.Object({
113
+ walletId: Type.String({
114
+ description: 'Unique identifier for the wallet',
115
+ }),
116
+ status: Type.Optional(
117
+ Type.Union([
118
+ Type.Literal('all', { description: 'All intents (open and closed)' }),
119
+ Type.Literal('open', { description: 'Only open/pending intents' }),
120
+ Type.Literal('closed', { description: 'Only filled/cancelled/expired intents' }),
121
+ ], {
122
+ description: 'Filter by intent status',
123
+ default: 'all',
124
+ })
125
+ ),
126
+ limit: Type.Optional(
127
+ Type.Number({
128
+ description: 'Number of items to return (default: 50, max: 100)',
129
+ default: 50,
130
+ minimum: 1,
131
+ maximum: 100,
132
+ })
133
+ ),
134
+ offset: Type.Optional(
135
+ Type.Number({
136
+ description: 'Number of items to skip (for pagination)',
137
+ default: 0,
138
+ minimum: 0,
139
+ })
140
+ ),
141
+ });
142
+
143
+ /**
144
+ * Schema for amped_list_wallets - List all configured wallets
145
+ */
146
+ const ListWalletsSchema = Type.Object({});
147
+
148
+ // ============================================================================
149
+ // Type Definitions
150
+ // ============================================================================
151
+
152
+ type SupportedChainsParams = Static<typeof SupportedChainsSchema>;
153
+ type SupportedTokensParams = Static<typeof SupportedTokensSchema>;
154
+ type WalletAddressParams = Static<typeof WalletAddressSchema>;
155
+ type MoneyMarketPositionsParams = Static<typeof MoneyMarketPositionsSchema>;
156
+ type ListWalletsParams = Static<typeof ListWalletsSchema>;
157
+ type MoneyMarketReservesParams = Static<typeof MoneyMarketReservesSchema>;
158
+ type CrossChainPositionsParams = Static<typeof CrossChainPositionsSchema>;
159
+ type UserIntentsParams = Static<typeof UserIntentsSchema>;
160
+
161
+ /**
162
+ * AgentTools interface for registering tools with the OpenClaw framework
163
+ */
164
+ interface AgentTools {
165
+ register(tool: {
166
+ name: string;
167
+ summary: string;
168
+ description?: string;
169
+ schema: unknown;
170
+ handler: (params: unknown) => Promise<unknown>;
171
+ }): void;
172
+ }
173
+
174
+ // Helper to wrap typed handlers for AgentTools registration
175
+ function wrapHandler<T>(handler: (params: T) => Promise<unknown>): (params: unknown) => Promise<unknown> {
176
+ return (params: unknown) => handler(params as T);
177
+ }
178
+
179
+ // ============================================================================
180
+ // Tool Implementations
181
+ // ============================================================================
182
+
183
+ /**
184
+ * Get supported spoke chains from SODAX configuration
185
+ */
186
+ async function handleSupportedChains(
187
+ _params: SupportedChainsParams
188
+ ): Promise<unknown> {
189
+ const sodax = getSodaxClient();
190
+ const chains = sodax.config.getSupportedSpokeChains();
191
+
192
+ // SDK may return chain IDs as strings or chain objects
193
+ return {
194
+ success: true,
195
+ chains: chains.map((chain: any) => {
196
+ // Handle both string IDs and chain objects
197
+ if (typeof chain === 'string') {
198
+ return {
199
+ id: chain,
200
+ name: chain,
201
+ type: 'evm',
202
+ isHub: chain === 'sonic',
203
+ nativeCurrency: undefined,
204
+ };
205
+ }
206
+ return {
207
+ id: chain.id || chain,
208
+ name: chain.name || chain.id || chain,
209
+ type: chain.type || 'evm',
210
+ isHub: (chain.id || chain) === 'sonic',
211
+ nativeCurrency: chain.nativeCurrency,
212
+ };
213
+ }),
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Get supported tokens for a specific module and chain
219
+ */
220
+ async function handleSupportedTokens(
221
+ params: SupportedTokensParams
222
+ ): Promise<unknown> {
223
+ const sodax = getSodaxClient();
224
+ const { module, chainId: rawChainId } = params;
225
+ const chainId = toSodaxChainId(rawChainId);
226
+
227
+ let tokens: Array<{
228
+ address: string;
229
+ symbol: string;
230
+ name: string;
231
+ decimals: number;
232
+ logoURI?: string;
233
+ }> = [];
234
+
235
+ // Helper to normalize token data
236
+ const normalizeToken = (token: any) => ({
237
+ address: token.address || '',
238
+ symbol: token.symbol || '',
239
+ name: token.name || token.symbol || '',
240
+ decimals: token.decimals || 18,
241
+ logoURI: token.logoURI || token.logoUri,
242
+ });
243
+
244
+ switch (module) {
245
+ case 'swaps': {
246
+ // Get supported swap tokens by chain ID
247
+ // SDK may require chainId to be cast to specific type
248
+ try {
249
+ const swapTokens = sodax.config.getSupportedSwapTokensByChainId(chainId as any);
250
+ tokens = (swapTokens || []).map(normalizeToken);
251
+ } catch (e) {
252
+ console.warn('[discovery] Failed to get swap tokens:', e);
253
+ tokens = [];
254
+ }
255
+ break;
256
+ }
257
+
258
+
259
+ case 'bridge': {
260
+ // Get bridgeable tokens via hub assets
261
+ // Hub assets represent tokens that can be bridged between chains
262
+ // Reference: sodax-frontend uses getHubAssets() for bridge token discovery
263
+ try {
264
+ const hubAssets = sodax.config.getHubAssets();
265
+
266
+ // Check if this is the hub chain (Sonic)
267
+ const isHubChain = rawChainId === 'sonic' || chainId === 'sonic';
268
+
269
+ if (isHubChain) {
270
+ // For Sonic (hub), show all bridgeable assets from all spoke chains
271
+ // These are the assets that can be bridged FROM Sonic to other chains
272
+ const allTokens: typeof tokens = [];
273
+ const seenAddresses = new Set<string>();
274
+
275
+ for (const spokeChainId of Object.keys(hubAssets)) {
276
+ const chainAssets = hubAssets[spokeChainId as keyof typeof hubAssets] || {};
277
+ for (const asset of Object.values(chainAssets)) {
278
+ // Add the hub asset (on Sonic) - dedupe by hub address
279
+ const hubAddress = (asset as any).asset || (asset as any).hubAddress || (asset as any).address;
280
+ if (hubAddress && !seenAddresses.has(hubAddress.toLowerCase())) {
281
+ seenAddresses.add(hubAddress.toLowerCase());
282
+ allTokens.push(normalizeToken({
283
+ address: hubAddress,
284
+ symbol: (asset as any).symbol || '',
285
+ name: (asset as any).name || (asset as any).symbol || '',
286
+ decimals: (asset as any).decimals || 18,
287
+ logoURI: (asset as any).logoURI || (asset as any).logoUri,
288
+ }));
289
+ }
290
+ }
291
+ }
292
+ tokens = allTokens;
293
+ } else {
294
+ // For spoke chains, get assets bridgeable from that specific chain
295
+ const chainAssets = hubAssets[chainId as keyof typeof hubAssets] || {};
296
+ tokens = Object.values(chainAssets).map((asset: any) => normalizeToken({
297
+ address: (asset as any).asset || (asset as any).address || (asset as any).originalAddress || '',
298
+ symbol: asset.symbol || '',
299
+ name: asset.name || asset.symbol || '',
300
+ decimals: (asset as any).decimal || (asset as any).decimals || 18,
301
+ logoURI: asset.logoURI || asset.logoUri,
302
+ }));
303
+ }
304
+ } catch (e) {
305
+ console.warn('[discovery] Failed to get bridge tokens:', e);
306
+ tokens = [];
307
+ }
308
+ break;
309
+ }
310
+
311
+ case 'moneyMarket': {
312
+ // Get money market supported tokens from config
313
+ // Reference: sodax-frontend ConfigService.getSupportedMoneyMarketTokensByChainId
314
+ try {
315
+ const mmTokens = sodax.config.getSupportedMoneyMarketTokensByChainId?.(chainId as any);
316
+ if (mmTokens && Array.isArray(mmTokens)) {
317
+ tokens = mmTokens.map(normalizeToken);
318
+ } else {
319
+ // Fallback: try supportedMoneyMarketTokens directly from config
320
+ const allMmTokens = (sodax.config as any).sodaxConfig?.supportedMoneyMarketTokens;
321
+ if (allMmTokens && allMmTokens[chainId]) {
322
+ tokens = allMmTokens[chainId].map(normalizeToken);
323
+ } else {
324
+ console.warn('[discovery] No money market tokens found for chain', chainId);
325
+ tokens = [];
326
+ }
327
+ }
328
+ } catch (e) {
329
+ console.warn('[discovery] Failed to get money market tokens:', e);
330
+ tokens = [];
331
+ }
332
+ break;
333
+ }
334
+
335
+ default:
336
+ throw new Error(`Unknown module: ${module}`);
337
+ }
338
+
339
+ return {
340
+ success: true,
341
+ module,
342
+ chainId,
343
+ tokens,
344
+ count: tokens.length,
345
+ };
346
+ }
347
+
348
+ /**
349
+ * Get wallet address by walletId
350
+ * Returns enhanced wallet info with source and supported chains
351
+ */
352
+ async function handleWalletAddress(
353
+ params: WalletAddressParams
354
+ ): Promise<unknown> {
355
+ const { walletId } = params;
356
+
357
+ // Get wallet from unified WalletManager
358
+ const walletManager = getWalletManager();
359
+ const wallet = await walletManager.resolve(walletId);
360
+
361
+ const address = await wallet.getAddress();
362
+
363
+ return {
364
+ success: true,
365
+ walletId: wallet.nickname,
366
+ address,
367
+ type: wallet.type,
368
+ chains: [...wallet.supportedChains],
369
+ };
370
+ }
371
+
372
+ /**
373
+ * Get user money market positions (humanized format)
374
+ */
375
+ async function handleMoneyMarketPositions(
376
+ params: MoneyMarketPositionsParams
377
+ ): Promise<unknown> {
378
+ const { walletId, chainId } = params;
379
+
380
+ // Get wallet from unified WalletManager
381
+ const walletManager = getWalletManager();
382
+ const wallet = await walletManager.resolve(walletId);
383
+ const walletAddress = await wallet.getAddress();
384
+
385
+ // Get spoke provider for this wallet and chain
386
+ const spokeProvider = await getSpokeProvider(walletId, chainId);
387
+
388
+ const sodax = getSodaxClient();
389
+
390
+ // IMPORTANT: getUserReservesHumanized() only returns raw balances without token metadata.
391
+ // To get token symbols/names, we must:
392
+ // 1. Fetch getReservesHumanized() for token metadata
393
+ // 2. Format with formatReservesUSD(buildReserveDataWithPrice())
394
+ // 3. Join with formatUserSummary(buildUserSummaryRequest())
395
+ // Reference: sodax-frontend/packages/dapp-kit/src/hooks/mm/useUserFormattedSummary.ts
396
+
397
+ // Step 1: Fetch reserves with token metadata (symbols, names, decimals)
398
+ const reserves = await sodax.moneyMarket.data.getReservesHumanized();
399
+
400
+ // Step 2: Format reserves with USD prices
401
+ const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(
402
+ sodax.moneyMarket.data.buildReserveDataWithPrice(reserves)
403
+ );
404
+
405
+ // Step 3: Fetch user-specific balances
406
+ const userReservesResult = await sodax.moneyMarket.data.getUserReservesHumanized(
407
+ spokeProvider
408
+ );
409
+
410
+ // Step 4: Join reserves metadata with user balances via formatUserSummary
411
+ const userSummary = sodax.moneyMarket.data.formatUserSummary(
412
+ sodax.moneyMarket.data.buildUserSummaryRequest(reserves, formattedReserves, userReservesResult)
413
+ );
414
+
415
+ // Extract user reserves from the formatted summary
416
+ // The formatted summary has userReservesData with proper token metadata
417
+ const userReservesData = (userSummary as any).userReservesData || [];
418
+
419
+ // Format positions for readability
420
+ const positions = userReservesData.map((reserve: any) => {
421
+ // Get supply balance (underlyingBalance is the human-readable supply amount)
422
+ const supplyBalance = reserve.underlyingBalance || '0';
423
+ const supplyBalanceUsd = reserve.underlyingBalanceUSD || '0';
424
+
425
+ // Get borrow balance (variableBorrows is the human-readable borrow amount)
426
+ const borrowBalance = reserve.variableBorrows || reserve.totalBorrows || '0';
427
+ const borrowBalanceUsd = reserve.variableBorrowsUSD || reserve.totalBorrowsUSD || '0';
428
+
429
+ // Get APY values (formatted reserves have these)
430
+ const supplyApy = parseFloat(reserve.reserve?.supplyAPY || '0') * 100;
431
+ const borrowApy = parseFloat(reserve.reserve?.variableBorrowAPY || '0') * 100;
432
+
433
+ return {
434
+ token: {
435
+ address: reserve.underlyingAsset || reserve.reserve?.underlyingAsset || '',
436
+ symbol: reserve.reserve?.symbol || '',
437
+ name: reserve.reserve?.name || '',
438
+ decimals: reserve.reserve?.decimals || 18,
439
+ },
440
+ supply: {
441
+ balance: supplyBalance,
442
+ balanceUsd: supplyBalanceUsd,
443
+ apy: supplyApy,
444
+ collateral: reserve.usageAsCollateralEnabledOnUser ?? false,
445
+ },
446
+ borrow: {
447
+ balance: borrowBalance,
448
+ balanceUsd: borrowBalanceUsd,
449
+ apy: borrowApy,
450
+ },
451
+ // Health indicators
452
+ loanToValue: parseFloat(reserve.reserve?.baseLTVasCollateral || '0') / 10000,
453
+ liquidationThreshold: parseFloat(reserve.reserve?.reserveLiquidationThreshold || '0') / 10000,
454
+ };
455
+ });
456
+
457
+ // Filter to only positions with activity
458
+ const activePositions = positions.filter((p: any) =>
459
+ parseFloat(p.supply.balance) > 0 || parseFloat(p.borrow.balance) > 0
460
+ );
461
+
462
+ // Calculate summary metrics
463
+ const totalSupplyUsd = activePositions.reduce(
464
+ (sum: number, p: any) => sum + (parseFloat(p.supply.balanceUsd) || 0),
465
+ 0
466
+ );
467
+ const totalBorrowUsd = activePositions.reduce(
468
+ (sum: number, p: any) => sum + (parseFloat(p.borrow.balanceUsd) || 0),
469
+ 0
470
+ );
471
+ const netWorthUsd = totalSupplyUsd - totalBorrowUsd;
472
+ const healthFactor =
473
+ totalBorrowUsd > 0 ? totalSupplyUsd / totalBorrowUsd : Infinity;
474
+
475
+ return {
476
+ success: true,
477
+ walletId,
478
+ address: walletAddress,
479
+ chainId,
480
+ positions: activePositions,
481
+ summary: {
482
+ totalSupplyUsd: totalSupplyUsd.toFixed(2),
483
+ totalBorrowUsd: totalBorrowUsd.toFixed(2),
484
+ netWorthUsd: netWorthUsd.toFixed(2),
485
+ healthFactor: healthFactor === Infinity ? '∞' : healthFactor.toFixed(2),
486
+ positionCount: activePositions.length,
487
+ },
488
+ };
489
+ }
490
+
491
+ /**
492
+ * Get money market reserves (humanized format)
493
+ * Hub-centric: returns reserves across all markets
494
+ */
495
+ async function handleMoneyMarketReserves(
496
+ params: MoneyMarketReservesParams
497
+ ): Promise<unknown> {
498
+ const { chainId } = params;
499
+
500
+ const sodax = getSodaxClient();
501
+
502
+ // Get reserves in humanized format (hub-centric)
503
+ const reservesResult = await sodax.moneyMarket.data.getReservesHumanized();
504
+
505
+ // SDK may return ReservesDataHumanized object with .reservesData array or just array
506
+ const reservesArray = Array.isArray(reservesResult)
507
+ ? reservesResult
508
+ : (reservesResult as any).reservesData || [];
509
+
510
+ // Filter by chainId if provided
511
+ let filteredReserves = reservesArray;
512
+ if (chainId) {
513
+ filteredReserves = reservesArray.filter(
514
+ (r: any) => r.token?.chainId === chainId || r.hubChainId === chainId || r.chainId === chainId
515
+ );
516
+ }
517
+
518
+ // Format reserves for readability
519
+ const formattedReserves = filteredReserves.map((reserve: any) => ({
520
+ token: {
521
+ address: reserve.token?.address || reserve.underlyingAsset || '',
522
+ symbol: reserve.token?.symbol || reserve.symbol || '',
523
+ name: reserve.token?.name || reserve.name || '',
524
+ decimals: reserve.token?.decimals || reserve.decimals || 18,
525
+ chainId: reserve.token?.chainId || reserve.chainId || '',
526
+ },
527
+ liquidity: {
528
+ totalSupply: reserve.liquidity?.totalSupply || reserve.totalScaledVariableDebt || '0',
529
+ availableLiquidity: reserve.liquidity?.availableLiquidity || reserve.availableLiquidity || '0',
530
+ totalBorrow: reserve.liquidity?.totalBorrow || reserve.totalVariableDebt || '0',
531
+ utilizationRate: reserve.liquidity?.utilizationRate || reserve.utilizationRate || '0',
532
+ },
533
+ rates: {
534
+ supplyApy: reserve.rates?.supplyApy || reserve.supplyAPY || '0',
535
+ borrowApy: reserve.rates?.borrowApy || reserve.variableBorrowAPY || '0',
536
+ },
537
+ parameters: {
538
+ loanToValue: reserve.parameters?.loanToValue || reserve.baseLTVasCollateral || '0',
539
+ liquidationThreshold: reserve.parameters?.liquidationThreshold || reserve.reserveLiquidationThreshold || '0',
540
+ liquidationBonus: reserve.parameters?.liquidationBonus || reserve.reserveLiquidationBonus || '0',
541
+ },
542
+ hubChainId: reserve.hubChainId || 'sonic',
543
+ }));
544
+
545
+ // Calculate aggregate metrics
546
+ const totalAvailableLiquidity = formattedReserves.reduce(
547
+ (sum: number, r: any) => sum + (parseFloat(r.liquidity.availableLiquidity) || 0),
548
+ 0
549
+ );
550
+ const totalBorrowed = formattedReserves.reduce(
551
+ (sum: number, r: any) => sum + (parseFloat(r.liquidity.totalBorrow) || 0),
552
+ 0
553
+ );
554
+
555
+ return {
556
+ success: true,
557
+ chainId: chainId || 'all',
558
+ reserves: formattedReserves,
559
+ summary: {
560
+ reserveCount: formattedReserves.length,
561
+ totalAvailableLiquidity: totalAvailableLiquidity.toFixed(2),
562
+ totalBorrowed: totalBorrowed.toFixed(2),
563
+ globalUtilizationRate:
564
+ totalAvailableLiquidity + totalBorrowed > 0
565
+ ? (
566
+ (totalBorrowed / (totalAvailableLiquidity + totalBorrowed)) *
567
+ 100
568
+ ).toFixed(2) + '%'
569
+ : '0%',
570
+ },
571
+ };
572
+ }
573
+
574
+ // ============================================================================
575
+ // Cross-Chain Positions Tool
576
+ // ============================================================================
577
+
578
+ /**
579
+ * Get aggregated money market positions across all chains
580
+ *
581
+ * This provides a unified view of:
582
+ * - Total supply/borrow across all networks
583
+ * - Health factor and liquidation risk
584
+ * - Available borrowing power
585
+ * - Net position and APY
586
+ * - Risk metrics and recommendations
587
+ */
588
+ async function handleCrossChainPositions(
589
+ params: CrossChainPositionsParams
590
+ ): Promise<unknown> {
591
+ const { walletId, chainIds, includeZeroBalances, minUsdValue } = params;
592
+
593
+ console.log('[discovery:crossChainPositions] Aggregating positions', {
594
+ walletId,
595
+ chainIds: chainIds || 'all',
596
+ includeZeroBalances,
597
+ minUsdValue,
598
+ });
599
+
600
+ try {
601
+ const view = await aggregateCrossChainPositions(walletId, {
602
+ chainIds,
603
+ includeZeroBalances,
604
+ minUsdValue,
605
+ });
606
+
607
+ // Get recommendations
608
+ const recommendations = getPositionRecommendation(view);
609
+
610
+ // Format response
611
+ const response = {
612
+ success: true,
613
+ walletId: view.walletId,
614
+ address: view.address,
615
+ timestamp: view.timestamp,
616
+ summary: {
617
+ totalSupplyUsd: view.summary.totalSupplyUsd.toFixed(2),
618
+ totalBorrowUsd: view.summary.totalBorrowUsd.toFixed(2),
619
+ netWorthUsd: view.summary.netWorthUsd.toFixed(2),
620
+ availableBorrowUsd: view.summary.availableBorrowUsd.toFixed(2),
621
+ healthFactor: formatHealthFactor(view.summary.healthFactor),
622
+ healthFactorStatus: getHealthFactorStatus(view.summary.healthFactor),
623
+ liquidationRisk: view.summary.liquidationRisk,
624
+ weightedSupplyApy: `${(view.summary.weightedSupplyApy * 100).toFixed(2)}%`,
625
+ weightedBorrowApy: `${(view.summary.weightedBorrowApy * 100).toFixed(2)}%`,
626
+ netApy: `${(view.summary.netApy * 100).toFixed(2)}%`,
627
+ },
628
+ chainBreakdown: view.chainSummaries.map(cs => ({
629
+ chainId: cs.chainId,
630
+ supplyUsd: cs.supplyUsd.toFixed(2),
631
+ borrowUsd: cs.borrowUsd.toFixed(2),
632
+ netWorthUsd: cs.netWorthUsd.toFixed(2),
633
+ healthFactor: formatHealthFactor(cs.healthFactor),
634
+ positionCount: cs.positionCount,
635
+ })),
636
+ collateralUtilization: {
637
+ totalCollateralUsd: view.collateralUtilization.totalCollateralUsd.toFixed(2),
638
+ usedCollateralUsd: view.collateralUtilization.usedCollateralUsd.toFixed(2),
639
+ availableCollateralUsd: view.collateralUtilization.availableCollateralUsd.toFixed(2),
640
+ utilizationRate: `${view.collateralUtilization.utilizationRate.toFixed(2)}%`,
641
+ },
642
+ riskMetrics: {
643
+ maxLtv: `${(view.riskMetrics.maxLtv * 100).toFixed(2)}%`,
644
+ currentLtv: `${(view.riskMetrics.currentLtv * 100).toFixed(2)}%`,
645
+ bufferUntilLiquidation: `${view.riskMetrics.bufferUntilLiquidation.toFixed(2)}%`,
646
+ safeMaxBorrowUsd: view.riskMetrics.safeMaxBorrowUsd.toFixed(2),
647
+ },
648
+ positions: view.positions.map(pos => ({
649
+ chainId: pos.chainId,
650
+ token: pos.token,
651
+ supply: {
652
+ balance: pos.supply.balance,
653
+ balanceUsd: pos.supply.balanceUsd,
654
+ apy: `${(pos.supply.apy * 100).toFixed(2)}%`,
655
+ isCollateral: pos.supply.isCollateral,
656
+ },
657
+ borrow: {
658
+ balance: pos.borrow.balance,
659
+ balanceUsd: pos.borrow.balanceUsd,
660
+ apy: `${(pos.borrow.apy * 100).toFixed(2)}%`,
661
+ },
662
+ loanToValue: `${(pos.loanToValue * 100).toFixed(2)}%`,
663
+ liquidationThreshold: `${(pos.liquidationThreshold * 100).toFixed(2)}%`,
664
+ })),
665
+ recommendations,
666
+ };
667
+
668
+ console.log('[discovery:crossChainPositions] Aggregation complete', {
669
+ totalPositions: view.positions.length,
670
+ totalSupplyUsd: view.summary.totalSupplyUsd,
671
+ totalBorrowUsd: view.summary.totalBorrowUsd,
672
+ healthFactor: view.summary.healthFactor,
673
+ });
674
+
675
+ return response;
676
+ } catch (error) {
677
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
678
+ console.error('[discovery:crossChainPositions] Failed to aggregate positions', {
679
+ error: errorMessage,
680
+ walletId,
681
+ });
682
+ throw new Error(`Failed to aggregate cross-chain positions: ${errorMessage}`);
683
+ }
684
+ }
685
+
686
+ // ============================================================================
687
+ // User Intents Tool (SODAX API)
688
+ // ============================================================================
689
+
690
+ /**
691
+ * Get user intents from SODAX API
692
+ *
693
+ * Queries the backend API for intent history including:
694
+ * - Open/pending intents
695
+ * - Filled intents
696
+ * - Cancelled/expired intents
697
+ * - Event history for each intent
698
+ */
699
+ async function handleUserIntents(
700
+ params: UserIntentsParams
701
+ ): Promise<unknown> {
702
+ const { walletId, status = 'all', limit = 50, offset = 0 } = params;
703
+
704
+ console.log('[discovery:userIntents] Fetching user intents', {
705
+ walletId,
706
+ status,
707
+ limit,
708
+ offset,
709
+ });
710
+
711
+ try {
712
+ // Get wallet address from unified WalletManager
713
+ const walletManager = getWalletManager();
714
+ const wallet = await walletManager.resolve(walletId);
715
+ const walletAddress = await wallet.getAddress();
716
+
717
+ // Initialize API client
718
+ const apiClient = getSodaxApiClient();
719
+
720
+ // Determine filters based on status
721
+ let filters;
722
+ if (status === 'open') {
723
+ filters = { open: true };
724
+ } else if (status === 'closed') {
725
+ filters = { open: false };
726
+ }
727
+
728
+ // Fetch intents
729
+ const response = await apiClient.getUserIntents(
730
+ walletAddress,
731
+ { limit, offset },
732
+ filters
733
+ );
734
+
735
+ // Format response
736
+ const formattedIntents = response.items.map(intent => ({
737
+ intentHash: intent.intentHash,
738
+ txHash: intent.txHash,
739
+ chainId: intent.chainId,
740
+ blockNumber: intent.blockNumber,
741
+ status: intent.open ? 'open' : 'closed',
742
+ createdAt: intent.createdAt,
743
+ input: {
744
+ token: intent.intent.inputToken,
745
+ amount: intent.intent.inputAmount,
746
+ chainId: intent.intent.srcChain,
747
+ },
748
+ output: {
749
+ token: intent.intent.outputToken,
750
+ minAmount: intent.intent.minOutputAmount,
751
+ chainId: intent.intent.dstChain,
752
+ },
753
+ deadline: new Date(parseInt(intent.intent.deadline) * 1000).toISOString(),
754
+ allowPartialFill: intent.intent.allowPartialFill,
755
+ events: intent.events
756
+ .filter((event): event is typeof event & { intentState: NonNullable<typeof event.intentState> } =>
757
+ event.intentState != null
758
+ )
759
+ .map(event => ({
760
+ type: event.eventType,
761
+ txHash: event.txHash,
762
+ blockNumber: event.blockNumber,
763
+ state: {
764
+ remainingInput: event.intentState.remainingInput,
765
+ receivedOutput: event.intentState.receivedOutput,
766
+ pendingPayment: event.intentState.pendingPayment,
767
+ },
768
+ })),
769
+ }));
770
+
771
+ const result = {
772
+ success: true,
773
+ walletId,
774
+ address: walletAddress,
775
+ pagination: {
776
+ total: response.total,
777
+ offset: response.offset,
778
+ limit: response.limit,
779
+ hasMore: response.offset + response.items.length < response.total,
780
+ },
781
+ intents: formattedIntents,
782
+ summary: {
783
+ totalIntents: response.total,
784
+ returned: formattedIntents.length,
785
+ openIntents: formattedIntents.filter((i: { status: string }) => i.status === 'open').length,
786
+ closedIntents: formattedIntents.filter((i: { status: string }) => i.status === 'closed').length,
787
+ },
788
+ };
789
+
790
+ console.log('[discovery:userIntents] User intents fetched', {
791
+ total: response.total,
792
+ returned: formattedIntents.length,
793
+ });
794
+
795
+ return result;
796
+ } catch (error) {
797
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
798
+ console.error('[discovery:userIntents] Failed to fetch user intents', {
799
+ error: errorMessage,
800
+ walletId,
801
+ });
802
+ throw new Error(`Failed to fetch user intents: ${errorMessage}`);
803
+ }
804
+ }
805
+
806
+ // ============================================================================
807
+ // List Wallets Tool
808
+ // ============================================================================
809
+
810
+ /**
811
+ * List all configured wallets with their nicknames, types, and supported chains
812
+ */
813
+ async function handleListWallets(
814
+ _params: ListWalletsParams
815
+ ): Promise<unknown> {
816
+ console.log('[discovery:listWallets] Listing configured wallets');
817
+
818
+ const walletManager = getWalletManager();
819
+ const wallets = await walletManager.listWallets();
820
+
821
+ const formattedWallets = wallets.map(w => ({
822
+ nickname: w.nickname,
823
+ type: w.type,
824
+ address: w.address,
825
+ addressKnown: w.address !== '0x...',
826
+ supportedChains: w.chains,
827
+ isDefault: w.isDefault,
828
+ note: w.address === '0x...' && w.type === 'bankr'
829
+ ? 'Address pending - will be fetched on first use'
830
+ : undefined,
831
+ }));
832
+
833
+ const defaultWallet = await walletManager.getDefaultWalletName();
834
+
835
+ // Group by type for summary
836
+ const byType = {
837
+ 'evm-wallet-skill': formattedWallets.filter(w => w.type === 'evm-wallet-skill'),
838
+ 'bankr': formattedWallets.filter(w => w.type === 'bankr'),
839
+ 'env': formattedWallets.filter(w => w.type === 'env'),
840
+ };
841
+
842
+ // Check if Bankr is configured but wallet not found
843
+ const bankrKeyPresent = !!process.env.BANKR_API_KEY;
844
+ const bankrWalletFound = byType.bankr.length > 0;
845
+
846
+ return {
847
+ success: true,
848
+ wallets: formattedWallets,
849
+ defaultWallet,
850
+ count: formattedWallets.length,
851
+ summary: {
852
+ selfCustody: byType['evm-wallet-skill'].length + byType.env.length,
853
+ bankrManaged: byType.bankr.length,
854
+ },
855
+ sources: {
856
+ evmWalletSkill: byType['evm-wallet-skill'].length > 0,
857
+ bankr: bankrWalletFound,
858
+ bankrKeyConfigured: bankrKeyPresent,
859
+ env: byType.env.length > 0,
860
+ },
861
+ hint: wallets.length === 0
862
+ ? 'No wallets configured. Install evm-wallet-skill: git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill'
863
+ : bankrKeyPresent && !bankrWalletFound
864
+ ? 'Bankr API key found but wallet not loaded. Try "Add my bankr wallet" to register it.'
865
+ : 'Use wallet nickname in operations, e.g., "swap 100 USDC to ETH using main"',
866
+ };
867
+ }
868
+
869
+ // ============================================================================
870
+ // Tool Registration
871
+ // ============================================================================
872
+
873
+ /**
874
+ * Register all discovery tools with the agent tools registry
875
+ *
876
+ * @param agentTools - The OpenClaw AgentTools instance
877
+ */
878
+ export function registerDiscoveryTools(agentTools: AgentTools): void {
879
+ // 1. amped_supported_chains - Get supported spoke chains
880
+ agentTools.register({
881
+ name: 'amped_supported_chains',
882
+ summary:
883
+ 'Get a list of all supported spoke chains for swaps, bridging, and money market operations',
884
+ schema: SupportedChainsSchema,
885
+ handler: wrapHandler(handleSupportedChains),
886
+ });
887
+
888
+ // 2. amped_supported_tokens - Get supported tokens by module
889
+ agentTools.register({
890
+ name: 'amped_supported_tokens',
891
+ summary:
892
+ 'Get supported tokens for a specific module (swaps, bridge, or moneyMarket) on a given chain',
893
+ schema: SupportedTokensSchema,
894
+ handler: wrapHandler(handleSupportedTokens),
895
+ });
896
+
897
+ // 3. amped_wallet_address - Get wallet address
898
+ agentTools.register({
899
+ name: 'amped_wallet_address',
900
+ summary:
901
+ 'Get the resolved wallet address for a given walletId. Validates private key matches in execute mode.',
902
+ schema: WalletAddressSchema,
903
+ handler: wrapHandler(handleWalletAddress),
904
+ });
905
+
906
+ // 4. amped_money_market_positions - Get user positions (humanized)
907
+ agentTools.register({
908
+ name: 'amped_money_market_positions',
909
+ summary:
910
+ 'Get humanized money market positions for a wallet on a specific chain, including supply/borrow balances and health metrics',
911
+ schema: MoneyMarketPositionsSchema,
912
+ handler: wrapHandler(handleMoneyMarketPositions),
913
+ });
914
+
915
+ // 5. amped_money_market_reserves - Get market reserves (humanized)
916
+ agentTools.register({
917
+ name: 'amped_money_market_reserves',
918
+ summary:
919
+ 'Get humanized money market reserves data including liquidity, rates, and parameters. Hub-centric with optional chain filtering.',
920
+ schema: MoneyMarketReservesSchema,
921
+ handler: wrapHandler(handleMoneyMarketReserves),
922
+ });
923
+
924
+ // 6. amped_cross_chain_positions - Get aggregated positions across all chains
925
+ agentTools.register({
926
+ name: 'amped_cross_chain_positions',
927
+ summary:
928
+ 'Get a unified view of money market positions across ALL chains. Shows total supply/borrow, health factor, borrowing power, net APY, and risk metrics.',
929
+ description:
930
+ 'Aggregates money market positions across all supported chains to provide a comprehensive portfolio view. ' +
931
+ 'Includes: total supply/borrow in USD, health factor with risk status, available borrowing power, ' +
932
+ 'weighted APYs, collateral utilization, and personalized recommendations. ' +
933
+ 'This is the recommended tool for getting a complete picture of money market positions.',
934
+ schema: CrossChainPositionsSchema,
935
+ handler: wrapHandler(handleCrossChainPositions),
936
+ });
937
+
938
+ // 7. amped_user_intents - Query user intent history from SODAX API
939
+ agentTools.register({
940
+ name: 'amped_user_intents',
941
+ summary:
942
+ 'Query user swap intent history from SODAX backend API. Shows open, filled, and cancelled intents with event details.',
943
+ description:
944
+ 'Retrieves the complete intent history for a wallet from the SODAX backend API. ' +
945
+ 'Includes open intents (pending), filled intents (completed), and cancelled/expired intents. ' +
946
+ 'Each intent includes input/output tokens, amounts, chain IDs, and event history. ' +
947
+ 'Use this to track the status of past swaps and bridge operations.',
948
+ schema: UserIntentsSchema,
949
+ handler: wrapHandler(handleUserIntents),
950
+ });
951
+
952
+ // 8. amped_list_wallets - List all configured wallets
953
+ agentTools.register({
954
+ name: 'amped_list_wallets',
955
+ summary:
956
+ 'List all configured wallets with their nicknames, types, addresses, and supported chains.',
957
+ description:
958
+ 'Shows all available wallets from evm-wallet-skill (~/.evm-wallet.json), Bankr API, ' +
959
+ 'and environment variables (AMPED_OC_WALLETS_JSON). Each wallet has a nickname that can be ' +
960
+ 'used in operations like "swap 100 USDC using bankr" or "check balance on main". ' +
961
+ 'Also shows which chains each wallet supports.',
962
+ schema: ListWalletsSchema,
963
+ handler: wrapHandler(handleListWallets),
964
+ });
965
+ }
966
+
967
+ // Export schemas for testing and reuse
968
+ export {
969
+ SupportedChainsSchema,
970
+ SupportedTokensSchema,
971
+ WalletAddressSchema,
972
+ MoneyMarketPositionsSchema,
973
+ MoneyMarketReservesSchema,
974
+ CrossChainPositionsSchema,
975
+ UserIntentsSchema,
976
+ ListWalletsSchema,
977
+ };
978
+
979
+ // Export handlers for testing
980
+ export {
981
+ handleSupportedChains,
982
+ handleSupportedTokens,
983
+ handleWalletAddress,
984
+ handleMoneyMarketPositions,
985
+ handleMoneyMarketReserves,
986
+ handleCrossChainPositions,
987
+ handleUserIntents,
988
+ handleListWallets,
989
+ };