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