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,926 @@
1
+ /**
2
+ * Swap Tools for Amped DeFi Plugin
3
+ *
4
+ * Provides OpenClaw tools for cross-chain swap operations using SODAX SDK:
5
+ * - amped_swap_quote: Get exact-in/exact-out quotes
6
+ * - amped_swap_execute: Execute swaps with policy enforcement
7
+ * - amped_swap_status: Poll intent status
8
+ * - amped_swap_cancel: Cancel active intents
9
+ */
10
+
11
+ import { Static, Type } from '@sinclair/typebox';
12
+ import { serializeError } from '../utils/errorUtils';
13
+ // SDK types - using any for now due to beta API changes
14
+ import { Intent } from '@sodax/sdk';
15
+ type QuoteRequest = any;
16
+ type SwapQuote = any;
17
+ type IntentStatus = any;
18
+ import { getSodaxClient } from '../sodax/client';
19
+ import { getSpokeProvider } from '../providers/spokeProviderFactory';
20
+ import { PolicyEngine } from '../policy/policyEngine';
21
+ import { getWalletManager } from '../wallet/walletManager';
22
+ import type { AgentTools } from '../types';
23
+ import { toSodaxChainId } from '../wallet/types';
24
+ import { getSodaxApiClient } from '../utils/sodaxApi';
25
+
26
+ // ============================================================================
27
+ // SODAX API & Explorer Links
28
+ // ============================================================================
29
+
30
+ const SODAX_CANARY_API = 'https://canary-api.sodax.com/v1/be';
31
+
32
+ // Chain ID to block explorer mapping
33
+ const CHAIN_EXPLORERS: Record<string, string> = {
34
+ 'ethereum': 'https://etherscan.io/tx/',
35
+ 'base': 'https://basescan.org/tx/',
36
+ 'arbitrum': 'https://arbiscan.io/tx/',
37
+ 'optimism': 'https://optimistic.etherscan.io/tx/',
38
+ 'polygon': 'https://polygonscan.com/tx/',
39
+ 'sonic': 'https://sonicscan.org/tx/',
40
+ 'avalanche': 'https://snowtrace.io/tx/',
41
+ 'bsc': 'https://bscscan.com/tx/',
42
+ 'solana': 'https://solscan.io/tx/',
43
+ };
44
+
45
+ function getExplorerLink(chainId: string, txHash: string): string | undefined {
46
+ // Normalize chain ID (remove 0x prefix and suffix if present)
47
+ const normalizedChainId = chainId.replace(/^0x[\\da-f]+\\./, '').toLowerCase();
48
+ const explorer = CHAIN_EXPLORERS[normalizedChainId];
49
+ return explorer ? `${explorer}${txHash}` : undefined;
50
+ }
51
+
52
+ async function fetchIntentFromSodax(intentHash: string): Promise<any> {
53
+ try {
54
+ const response = await fetch(`${SODAX_CANARY_API}/intent/${intentHash}`);
55
+ if (!response.ok) return null;
56
+ return await response.json();
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Ensure intent hash is in hex format (0x prefixed)
64
+ */
65
+ function toHexIntentHash(hash: unknown): string | undefined {
66
+ if (!hash) return undefined;
67
+ const str = String(hash);
68
+ // Already hex format
69
+ if (str.startsWith('0x')) return str;
70
+ // Convert decimal BigInt string to hex
71
+ try {
72
+ return '0x' + BigInt(str).toString(16);
73
+ } catch {
74
+ return str; // Return as-is if conversion fails
75
+ }
76
+ }
77
+
78
+ function getSodaxScanUrl(txHash: string): string {
79
+ return `https://sodaxscan.com/messages/search?value=${txHash}`;
80
+ }
81
+
82
+ function getSodaxIntentApiUrl(intentHash: string): string {
83
+ return `${SODAX_CANARY_API}/intent/${intentHash}`;
84
+ }
85
+
86
+ // SODAX internal chain ID to block explorer mapping
87
+ const SODAX_CHAIN_EXPLORERS: Record<number, string> = {
88
+ 1: 'https://solscan.io/tx/', // Solana
89
+ 30: 'https://basescan.org/tx/', // Base
90
+ 146: 'https://sonicscan.org/tx/', // Sonic (hub)
91
+ 42161: 'https://arbiscan.io/tx/', // Arbitrum
92
+ 10: 'https://optimistic.etherscan.io/tx/', // Optimism
93
+ 137: 'https://polygonscan.com/tx/', // Polygon
94
+ 56: 'https://bscscan.com/tx/', // BSC
95
+ 43114: 'https://snowtrace.io/tx/', // Avalanche
96
+ };
97
+
98
+ /**
99
+ * Poll SODAX API until intent is delivered, then return delivery tx explorer link
100
+ */
101
+ async function pollForDelivery(
102
+ intentHash: string,
103
+ timeoutMs: number = 60000,
104
+ pollIntervalMs: number = 3000
105
+ ): Promise<{ delivered: boolean; deliveryTxHash?: string; deliveryExplorer?: string; dstChainId?: number }> {
106
+ const startTime = Date.now();
107
+
108
+ while (Date.now() - startTime < timeoutMs) {
109
+ try {
110
+ const response = await fetch(`${SODAX_CANARY_API}/intent/${intentHash}`);
111
+ if (!response.ok) {
112
+ await new Promise(r => setTimeout(r, pollIntervalMs));
113
+ continue;
114
+ }
115
+
116
+ const data: any = await response.json();
117
+
118
+ // Check if intent is filled (closed)
119
+ if (data.open === false && data.events?.length > 0) {
120
+ const fillEvent = data.events.find((e: any) => e.eventType === 'intent-filled');
121
+ if (fillEvent) {
122
+ const dstChainId = data.intent?.dstChain;
123
+ const explorer = SODAX_CHAIN_EXPLORERS[dstChainId] || '';
124
+
125
+ return {
126
+ delivered: true,
127
+ deliveryTxHash: fillEvent.txHash,
128
+ deliveryExplorer: explorer ? `${explorer}${fillEvent.txHash}` : undefined,
129
+ dstChainId
130
+ };
131
+ }
132
+ }
133
+
134
+ await new Promise(r => setTimeout(r, pollIntervalMs));
135
+ } catch {
136
+ await new Promise(r => setTimeout(r, pollIntervalMs));
137
+ }
138
+ }
139
+
140
+ return { delivered: false };
141
+ }
142
+
143
+ import { resolveToken, getTokenInfo } from '../utils/tokenResolver';
144
+
145
+ // ============================================================================
146
+ // TypeBox Schemas
147
+ // ============================================================================
148
+
149
+ const SwapTypeSchema = Type.Union([
150
+ Type.Literal('exact_input'),
151
+ Type.Literal('exact_output')
152
+ ]);
153
+
154
+ const SwapQuoteRequestSchema = Type.Object({
155
+ walletId: Type.String(),
156
+ srcChainId: Type.String(),
157
+ dstChainId: Type.String(),
158
+ srcToken: Type.String(),
159
+ dstToken: Type.String(),
160
+ amount: Type.String(),
161
+ type: SwapTypeSchema,
162
+ slippageBps: Type.Number({ default: 50, minimum: 0, maximum: 10000 }),
163
+ recipient: Type.Optional(Type.String({
164
+ description: 'Recipient address on destination chain. For cross-chain swaps to Solana, provide a Solana base58 address. Defaults to wallet address if omitted.'
165
+ }))
166
+ });
167
+
168
+ // Result schema for documentation (not used at runtime)
169
+ const _SwapQuoteResultSchema = Type.Object({
170
+ inputAmount: Type.String(),
171
+ outputAmount: Type.String(),
172
+ srcToken: Type.String(),
173
+ dstToken: Type.String(),
174
+ srcChainId: Type.String(),
175
+ dstChainId: Type.String(),
176
+ slippageBps: Type.Number(),
177
+ deadline: Type.Number(),
178
+ fees: Type.Object({
179
+ solverFee: Type.String(),
180
+ protocolFee: Type.Optional(Type.String()),
181
+ partnerFee: Type.Optional(Type.String())
182
+ }),
183
+ minOutputAmount: Type.Optional(Type.String()),
184
+ maxInputAmount: Type.Optional(Type.String()),
185
+ recipient: Type.Optional(Type.String())
186
+ });
187
+ void _SwapQuoteResultSchema; // Suppress unused warning
188
+
189
+ const SwapExecuteParamsSchema = Type.Object({
190
+ walletId: Type.String(),
191
+ quote: Type.Object({
192
+ srcChainId: Type.String(),
193
+ dstChainId: Type.String(),
194
+ srcToken: Type.String(),
195
+ dstToken: Type.String(),
196
+ inputAmount: Type.String(),
197
+ outputAmount: Type.String(),
198
+ slippageBps: Type.Number(),
199
+ deadline: Type.Number(),
200
+ minOutputAmount: Type.Optional(Type.String()),
201
+ maxInputAmount: Type.Optional(Type.String()),
202
+ recipient: Type.Optional(Type.String())
203
+ }),
204
+ maxSlippageBps: Type.Optional(Type.Number({ minimum: 0, maximum: 10000 })),
205
+ policyId: Type.Optional(Type.String()),
206
+ skipSimulation: Type.Optional(Type.Boolean({ default: false })),
207
+ timeoutMs: Type.Optional(Type.Number({ default: 120000 }))
208
+ });
209
+
210
+ const SwapExecuteResultSchema = Type.Object({
211
+ spokeTxHash: Type.String(),
212
+ hubTxHash: Type.Optional(Type.String()),
213
+ intentHash: Type.Optional(Type.String()),
214
+ status: Type.String(),
215
+ message: Type.Optional(Type.String())
216
+ });
217
+
218
+ const SwapStatusParamsSchema = Type.Object({
219
+ txHash: Type.Optional(Type.String()),
220
+ intentHash: Type.Optional(Type.String())
221
+ });
222
+
223
+ const SwapStatusResultSchema = Type.Object({
224
+ status: Type.String(),
225
+ intentHash: Type.Optional(Type.String()),
226
+ spokeTxHash: Type.Optional(Type.String()),
227
+ hubTxHash: Type.Optional(Type.String()),
228
+ filledAmount: Type.Optional(Type.String()),
229
+ error: Type.Optional(Type.String()),
230
+ createdAt: Type.Optional(Type.Number()),
231
+ expiresAt: Type.Optional(Type.Number())
232
+ });
233
+
234
+ const SwapCancelParamsSchema = Type.Object({
235
+ walletId: Type.String(),
236
+ intent: Type.Object({
237
+ id: Type.String(),
238
+ srcChainId: Type.String(),
239
+ dstChainId: Type.String(),
240
+ srcToken: Type.String(),
241
+ dstToken: Type.String(),
242
+ amount: Type.String(),
243
+ deadline: Type.Number()
244
+ }),
245
+ srcChainId: Type.String()
246
+ });
247
+
248
+ const SwapCancelResultSchema = Type.Object({
249
+ success: Type.Boolean(),
250
+ txHash: Type.Optional(Type.String()),
251
+ message: Type.String()
252
+ });
253
+
254
+ // ============================================================================
255
+ // Type Definitions
256
+ // ============================================================================
257
+
258
+ type SwapQuoteRequest = Static<typeof SwapQuoteRequestSchema>;
259
+ type SwapExecuteParams = Static<typeof SwapExecuteParamsSchema>;
260
+ type SwapStatusParams = Static<typeof SwapStatusParamsSchema>;
261
+ type SwapCancelParams = Static<typeof SwapCancelParamsSchema>;
262
+
263
+ // ============================================================================
264
+ // Swap Quote Tool
265
+ // ============================================================================
266
+
267
+ async function handleSwapQuote(params: SwapQuoteRequest): Promise<Record<string, unknown>> {
268
+ const requestId = generateRequestId();
269
+ const startTime = Date.now();
270
+
271
+ try {
272
+ const sodaxClient = getSodaxClient();
273
+
274
+ // Resolve token symbols to addresses
275
+ const srcTokenAddr = await resolveToken(params.srcChainId, params.srcToken);
276
+ const dstTokenAddr = await resolveToken(params.dstChainId, params.dstToken);
277
+
278
+ // Get token info for decimals
279
+ const srcTokenInfo = await getTokenInfo(params.srcChainId, srcTokenAddr);
280
+ const dstTokenInfo = await getTokenInfo(params.dstChainId, dstTokenAddr);
281
+
282
+ // Get token config to determine decimals for amount conversion
283
+ const configService = (sodaxClient as any).configService;
284
+ const decimals = srcTokenInfo?.decimals ?? 18; // Default to 18 (most EVM tokens) if not found
285
+
286
+ // Convert human-readable amount to raw amount (bigint)
287
+ const amountFloat = parseFloat(params.amount);
288
+ const rawAmount = BigInt(Math.floor(amountFloat * Math.pow(10, decimals)));
289
+
290
+ // Build SDK-compatible request with snake_case parameters
291
+ const quoteRequest = {
292
+ token_src: srcTokenAddr,
293
+ token_src_blockchain_id: toSodaxChainId(params.srcChainId),
294
+ token_dst: dstTokenAddr,
295
+ token_dst_blockchain_id: toSodaxChainId(params.dstChainId),
296
+ amount: rawAmount,
297
+ quote_type: params.type
298
+ };
299
+
300
+ console.log('[swap_quote] SDK request:', JSON.stringify(quoteRequest, (k, v) => typeof v === 'bigint' ? v.toString() : v));
301
+
302
+ const quoteResult = await (sodaxClient as any).swaps.getQuote(quoteRequest);
303
+
304
+ // Handle Result type from SDK
305
+ if (quoteResult.ok === false) {
306
+ const errorMsg = quoteResult.error instanceof Error
307
+ ? quoteResult.error.message
308
+ : typeof quoteResult.error === 'string'
309
+ ? quoteResult.error
310
+ : JSON.stringify(quoteResult.error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);
311
+ throw new Error(`Quote failed: ${errorMsg}`);
312
+ }
313
+
314
+ const quote = quoteResult.ok ? quoteResult.value : quoteResult;
315
+
316
+ console.log('[swap_quote] SDK response:', JSON.stringify(quote, (k, v) => typeof v === 'bigint' ? v.toString() : v));
317
+
318
+ // Get output token decimals with multiple fallbacks
319
+ // SDK returns quoted_amount as bigint - convert to human-readable string
320
+ let dstDecimals = (quote as any).token_dst_decimals || (quote as any).tokenDstDecimals;
321
+
322
+ if (!dstDecimals && dstTokenInfo) {
323
+ dstDecimals = dstTokenInfo.decimals;
324
+ }
325
+
326
+ if (!dstDecimals) {
327
+ // Hardcoded decimals for common stablecoins
328
+ const KNOWN_DECIMALS: Record<string, number> = {
329
+ usdc: 6, USDC: 6, usdt: 6, USDT: 6, sol: 9, SOL: 9,
330
+ dai: 18, DAI: 18, bnusd: 18, bnUSD: 18
331
+ };
332
+ const tokenSymbol = params.dstToken.toUpperCase();
333
+ dstDecimals = KNOWN_DECIMALS[tokenSymbol] || 18;
334
+ console.warn(`[swap_quote] Using fallback decimals (${dstDecimals}) for token ${params.dstToken}`);
335
+ }
336
+ const quotedAmount = quote.quoted_amount || quote.quotedAmount || quote.outputAmount;
337
+ const outputAmountStr = quotedAmount
338
+ ? (Number(quotedAmount) / Math.pow(10, dstDecimals)).toString()
339
+ : '0';
340
+
341
+ // Normalize and return quote (SDK uses snake_case, we return camelCase)
342
+ const result = {
343
+ inputAmount: params.amount,
344
+ outputAmount: outputAmountStr,
345
+ srcToken: srcTokenAddr,
346
+ dstToken: dstTokenAddr,
347
+ srcChainId: params.srcChainId,
348
+ dstChainId: params.dstChainId,
349
+ slippageBps: params.slippageBps,
350
+ deadline: quote.deadline || calculateDeadline(300), // 5 min default
351
+ fees: {
352
+ solverFee: quote.solver_fee || quote.fees?.solverFee || '0',
353
+ protocolFee: quote.protocol_fee || quote.fees?.protocolFee,
354
+ partnerFee: quote.partner_fee || quote.fees?.partnerFee
355
+ },
356
+ minOutputAmount: quote.min_output_amount || quote.minOutputAmount,
357
+ maxInputAmount: quote.max_input_amount || quote.maxInputAmount,
358
+ recipient: params.recipient, // Pass through for execute
359
+ // Include raw SDK response for debugging
360
+ _raw: JSON.parse(JSON.stringify(quote, (k, v) => typeof v === 'bigint' ? v.toString() : v))
361
+ };
362
+
363
+ logStructured({
364
+ requestId,
365
+ opType: 'swap_quote',
366
+ walletId: params.walletId,
367
+ chainIds: [params.srcChainId, params.dstChainId],
368
+ tokenAddresses: [params.srcToken, params.dstToken],
369
+ durationMs: Date.now() - startTime,
370
+ success: true
371
+ });
372
+
373
+ return result;
374
+ } catch (error) {
375
+ logStructured({
376
+ requestId,
377
+ opType: 'swap_quote',
378
+ walletId: params.walletId,
379
+ chainIds: [params.srcChainId, params.dstChainId],
380
+ durationMs: Date.now() - startTime,
381
+ success: false,
382
+ error: error instanceof Error ? error.message : String(error)
383
+ });
384
+
385
+ throw new Error(`Failed to get swap quote: ${error instanceof Error ? error.message : String(error)}`);
386
+ }
387
+ }
388
+
389
+ // ============================================================================
390
+ // Swap Execute Tool
391
+ // ============================================================================
392
+
393
+ async function handleSwapExecute(params: SwapExecuteParams): Promise<Record<string, unknown>> {
394
+ const requestId = generateRequestId();
395
+ const startTime = Date.now();
396
+
397
+ try {
398
+ // 1. Initialize dependencies
399
+ const policyEngine = new PolicyEngine();
400
+ const walletManager = getWalletManager();
401
+ const sodaxClient = getSodaxClient();
402
+
403
+ // Resolve token symbols to addresses
404
+ const srcTokenAddr = await resolveToken(params.quote.srcChainId, params.quote.srcToken);
405
+ const dstTokenAddr = await resolveToken(params.quote.dstChainId, params.quote.dstToken);
406
+
407
+ // Get token info for decimals
408
+ const srcTokenInfo = await getTokenInfo(params.quote.srcChainId, srcTokenAddr);
409
+ const dstTokenInfo = await getTokenInfo(params.quote.dstChainId, dstTokenAddr);
410
+
411
+ // 2. Resolve wallet
412
+ const wallet = await walletManager.resolve(params.walletId);
413
+ const walletAddress = await wallet.getAddress();
414
+
415
+ // 3. Policy check
416
+ const policyCheck = await policyEngine.checkSwap({
417
+ walletId: params.walletId,
418
+ srcChainId: params.quote.srcChainId,
419
+ dstChainId: params.quote.dstChainId,
420
+ srcToken: params.quote.srcToken,
421
+ dstToken: params.quote.dstToken,
422
+ inputAmount: params.quote.inputAmount,
423
+ slippageBps: params.maxSlippageBps || params.quote.slippageBps,
424
+ policyId: params.policyId
425
+ });
426
+
427
+ if (!policyCheck.allowed) {
428
+ throw new Error(`Policy check failed: ${policyCheck.reason}`);
429
+ }
430
+
431
+ // 4. Get spoke provider for source chain
432
+ const spokeProvider = await getSpokeProvider(
433
+ params.walletId,
434
+ params.quote.srcChainId
435
+ );
436
+
437
+ // 5. Convert amounts to bigint FIRST (needed for intentParams)
438
+ const srcDecimals = srcTokenInfo?.decimals ?? 18;
439
+ const dstDecimals = dstTokenInfo?.decimals ?? 18;
440
+ const inputAmountRaw = BigInt(Math.floor(parseFloat(params.quote.inputAmount) * Math.pow(10, srcDecimals)));
441
+ const outputAmountRaw = BigInt(Math.floor(parseFloat(params.quote.outputAmount) * Math.pow(10, dstDecimals)));
442
+
443
+ // Calculate minOutputAmount with slippage
444
+ const slippageBps = params.maxSlippageBps || params.quote.slippageBps || 100;
445
+ const minOutputAmountRaw = outputAmountRaw - (outputAmountRaw * BigInt(slippageBps) / 10000n);
446
+
447
+ console.log("[swap_execute] Amount conversion:", {
448
+ inputAmount: params.quote.inputAmount,
449
+ inputAmountRaw: inputAmountRaw.toString(),
450
+ outputAmount: params.quote.outputAmount,
451
+ outputAmountRaw: outputAmountRaw.toString(),
452
+ minOutputAmountRaw: minOutputAmountRaw.toString(),
453
+ srcDecimals,
454
+ dstDecimals
455
+ });
456
+
457
+ // 6. Build intentParams (used for allowance check, approval, and swap)
458
+ const intentParams = {
459
+ srcAddress: walletAddress,
460
+ dstAddress: params.quote.recipient || walletAddress,
461
+ srcChain: toSodaxChainId(params.quote.srcChainId),
462
+ dstChain: toSodaxChainId(params.quote.dstChainId),
463
+ inputToken: srcTokenAddr,
464
+ outputToken: dstTokenAddr,
465
+ inputAmount: inputAmountRaw,
466
+ minOutputAmount: minOutputAmountRaw,
467
+ deadline: BigInt(params.quote.deadline),
468
+ allowPartialFill: false,
469
+ solver: "0x0000000000000000000000000000000000000000" as `0x${string}`,
470
+ data: "0x" as `0x${string}`
471
+ };
472
+
473
+ // 7. Check allowance using SDK's expected API
474
+ let allowanceValid = false;
475
+ try {
476
+ const allowanceResult = await (sodaxClient as any).swaps.isAllowanceValid({
477
+ intentParams,
478
+ spokeProvider
479
+ });
480
+ allowanceValid = allowanceResult?.ok ? allowanceResult.value : !!allowanceResult;
481
+ } catch (e) {
482
+ console.warn('[swap_execute] Allowance check failed, assuming approval needed:', e);
483
+ allowanceValid = false;
484
+ }
485
+
486
+ // 8. Approve if needed using SDK's expected API
487
+ if (!allowanceValid) {
488
+ logStructured({
489
+ requestId,
490
+ opType: 'swap_approve',
491
+ walletId: params.walletId,
492
+ chainId: params.quote.srcChainId,
493
+ token: srcTokenAddr,
494
+ message: 'Token approval required'
495
+ });
496
+
497
+ const approvalResult = await (sodaxClient as any).swaps.approve({
498
+ intentParams,
499
+ spokeProvider
500
+ });
501
+
502
+ const approvalTx = approvalResult?.ok ? approvalResult.value : approvalResult;
503
+
504
+ // Wait for approval confirmation if possible
505
+ if ((spokeProvider as any).walletProvider?.waitForTransactionReceipt && approvalTx) {
506
+ await (spokeProvider as any).walletProvider.waitForTransactionReceipt(approvalTx);
507
+ }
508
+
509
+ logStructured({
510
+ requestId,
511
+ opType: 'swap_approve',
512
+ walletId: params.walletId,
513
+ chainId: params.quote.srcChainId,
514
+ token: srcTokenAddr,
515
+ approvalTx: String(approvalTx),
516
+ success: true
517
+ });
518
+ }
519
+
520
+ // 9. Execute swap
521
+ const swapResult = await (sodaxClient as any).swaps.swap({
522
+ intentParams,
523
+ spokeProvider,
524
+ skipSimulation: params.skipSimulation || false,
525
+ timeout: params.timeoutMs || 120000
526
+ });
527
+
528
+ // Handle Result type from SDK
529
+ if (swapResult.ok === false) {
530
+ const errorMsg = swapResult.error instanceof Error
531
+ ? swapResult.error.message
532
+ : typeof swapResult.error === 'string'
533
+ ? swapResult.error
534
+ : JSON.stringify(swapResult.error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);
535
+ throw new Error(`Swap failed: ${errorMsg}`);
536
+ }
537
+
538
+ const value = swapResult.ok ? swapResult.value : swapResult;
539
+
540
+ // SDK may return [response, intent, deliveryInfo] tuple
541
+ const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];
542
+
543
+ // Extract internal tracking info
544
+ const srcTxHash = deliveryInfo?.srcTxHash;
545
+ const intentHash = toHexIntentHash((solverResponse as any)?.intent_hash) || toHexIntentHash(intent?.intentId);
546
+
547
+ // Poll for delivery confirmation (wait up to 60s)
548
+ let deliveryResult: { delivered: boolean; deliveryExplorer?: string } = { delivered: false };
549
+ if (intentHash) {
550
+ console.log('[swap_execute] Waiting for delivery confirmation...');
551
+ deliveryResult = await pollForDelivery(intentHash, 60000, 3000);
552
+ }
553
+
554
+ // Build user-friendly result
555
+ const result = {
556
+ status: deliveryResult.delivered ? 'delivered' : 'submitted',
557
+ message: deliveryResult.delivered
558
+ ? 'Swap completed! Funds delivered to destination.'
559
+ : 'Swap submitted, awaiting cross-chain delivery...',
560
+ // User-friendly tracking link
561
+ sodaxScanUrl: srcTxHash ? getSodaxScanUrl(srcTxHash) : undefined,
562
+ // Source chain: where user initiated the swap
563
+ // Destination chain: where user RECEIVED funds
564
+ initiationTx: srcTxHash ? getExplorerLink(params.quote.srcChainId, srcTxHash) : undefined,
565
+ receiptTx: deliveryResult.deliveryExplorer,
566
+ };
567
+
568
+ logStructured({
569
+ requestId,
570
+ opType: 'swap_execute',
571
+ walletId: params.walletId,
572
+ chainIds: [params.quote.srcChainId, params.quote.dstChainId],
573
+ tokenAddresses: [params.quote.srcToken, params.quote.dstToken],
574
+ durationMs: Date.now() - startTime,
575
+ success: true
576
+ });
577
+
578
+ return result;
579
+ } catch (error) {
580
+ logStructured({
581
+ requestId,
582
+ opType: 'swap_execute',
583
+ walletId: params.walletId,
584
+ chainIds: [params.quote.srcChainId, params.quote.dstChainId],
585
+ durationMs: Date.now() - startTime,
586
+ success: false,
587
+ error: error instanceof Error ? error.message : String(error)
588
+ });
589
+
590
+ throw new Error(`Swap execution failed: ${error instanceof Error ? error.message : String(error)}`);
591
+ }
592
+ }
593
+
594
+ // ============================================================================
595
+ // Swap Status Tool
596
+ // ============================================================================
597
+
598
+
599
+ async function handleSwapStatus(params: SwapStatusParams): Promise<Record<string, unknown>> {
600
+ const requestId = generateRequestId();
601
+ const startTime = Date.now();
602
+
603
+ try {
604
+ if (!params.txHash && !params.intentHash) {
605
+ throw new Error('Either txHash or intentHash must be provided');
606
+ }
607
+
608
+ // Use our SodaxApiClient for Backend API access (not SDK)
609
+ const sodaxApi = getSodaxApiClient();
610
+
611
+ let intentData: any = null;
612
+
613
+ // Try intentHash first (most reliable)
614
+ if (params.intentHash) {
615
+ try {
616
+ intentData = await sodaxApi.getIntentByHash(params.intentHash);
617
+ } catch (err) {
618
+ console.warn(`[swap_status] getIntentByHash failed: ${err instanceof Error ? err.message : String(err)}`);
619
+ }
620
+ }
621
+
622
+ // If intentHash lookup failed or wasn't provided, try txHash
623
+ // Note: txHash should be from the HUB chain (Sonic), not spoke chain
624
+ if (!intentData && params.txHash) {
625
+ try {
626
+ intentData = await sodaxApi.getIntentByTxHash(params.txHash);
627
+ } catch (err) {
628
+ // txHash lookup failed - provide helpful error message
629
+ const errorMsg = err instanceof Error ? err.message : String(err);
630
+ throw new Error(
631
+ `Unable to find intent for txHash: ${params.txHash}. ` +
632
+ `Note: The txHash must be from the HUB chain (Sonic), not the spoke chain. ` +
633
+ `If you have a spoke chain txHash (Base, Arbitrum, etc.), use amped_user_intents to find the intent first. ` +
634
+ `Error: ${errorMsg}`
635
+ );
636
+ }
637
+ }
638
+
639
+ if (!intentData) {
640
+ throw new Error('Unable to retrieve swap status. Provide a valid intentHash or hub chain txHash.');
641
+ }
642
+
643
+ // Extract intent details
644
+ const intentHash = intentData.intentHash;
645
+ const hubTxHash = intentData.txHash; // This is the hub chain tx that created the intent
646
+ const isOpen = intentData.open;
647
+ const intent = intentData.intent;
648
+
649
+ // Determine status from intent state
650
+ let status = 'unknown';
651
+ if (intentData.open === true) {
652
+ status = 'pending';
653
+ } else if (intentData.open === false) {
654
+ // Check events to determine if filled or cancelled/expired
655
+ const filledEvent = intentData.events?.find((e: any) => e.eventType === 'intent-filled');
656
+ const cancelledEvent = intentData.events?.find((e: any) =>
657
+ e.eventType === 'intent-cancelled' || e.eventType === 'intent-expired'
658
+ );
659
+ if (filledEvent) {
660
+ status = 'filled';
661
+ } else if (cancelledEvent) {
662
+ status = cancelledEvent.eventType === 'intent-cancelled' ? 'cancelled' : 'expired';
663
+ } else {
664
+ status = 'closed';
665
+ }
666
+ }
667
+
668
+ // Extract fulfillment details if available
669
+ const filledEvent = intentData.events?.find((e: any) => e.eventType === 'intent-filled');
670
+ const fulfillmentTxHash = filledEvent?.txHash;
671
+ const receivedOutput = filledEvent?.intentState?.receivedOutput;
672
+
673
+ // Build result
674
+ const result: Record<string, unknown> = {
675
+ status,
676
+ intentHash,
677
+ hubTxHash, // Hub chain tx that created the intent
678
+ spokeTxHash: params.txHash !== hubTxHash ? params.txHash : undefined, // Original spoke tx if different
679
+ open: isOpen,
680
+ // Intent details
681
+ srcChain: intent?.srcChain,
682
+ dstChain: intent?.dstChain,
683
+ inputToken: intent?.inputToken,
684
+ outputToken: intent?.outputToken,
685
+ inputAmount: intent?.inputAmount,
686
+ minOutputAmount: intent?.minOutputAmount,
687
+ receivedOutput: receivedOutput,
688
+ deadline: intent?.deadline ? new Date(parseInt(intent.deadline) * 1000).toISOString() : undefined,
689
+ createdAt: intentData.createdAt,
690
+ // Fulfillment details
691
+ fulfillmentTxHash,
692
+ fulfillmentChain: intent?.dstChain,
693
+ // Tracking links
694
+ sodaxScanUrl: `https://sodaxscan.com/intents/${intentHash}`,
695
+ };
696
+
697
+ logStructured({
698
+ requestId,
699
+ opType: 'swap_status',
700
+ intentHash,
701
+ txHash: params.txHash,
702
+ status,
703
+ durationMs: Date.now() - startTime,
704
+ success: true
705
+ });
706
+
707
+ return result;
708
+ } catch (error) {
709
+ logStructured({
710
+ requestId,
711
+ opType: 'swap_status',
712
+ intentHash: params.intentHash,
713
+ txHash: params.txHash,
714
+ durationMs: Date.now() - startTime,
715
+ success: false,
716
+ error: error instanceof Error ? error.message : String(error)
717
+ });
718
+
719
+ throw new Error(`Failed to get swap status: ${error instanceof Error ? error.message : String(error)}`);
720
+ }
721
+ }
722
+
723
+ // ============================================================================
724
+ // Swap Cancel Tool
725
+ // ============================================================================
726
+
727
+ async function handleSwapCancel(params: SwapCancelParams): Promise<Record<string, unknown>> {
728
+ const requestId = generateRequestId();
729
+ const startTime = Date.now();
730
+
731
+ try {
732
+ const walletManager = getWalletManager();
733
+ const sodaxClient = getSodaxClient();
734
+
735
+ // Resolve token symbols to addresses
736
+ const srcTokenAddr = await resolveToken(params.intent.srcChainId, params.intent.srcToken);
737
+ const dstTokenAddr = await resolveToken(params.intent.dstChainId, params.intent.dstToken);
738
+
739
+ // Get token info for decimals
740
+ const srcTokenInfo = await getTokenInfo(params.intent.srcChainId, srcTokenAddr);
741
+ const dstTokenInfo = await getTokenInfo(params.intent.dstChainId, dstTokenAddr);
742
+
743
+ // Resolve wallet (validates it exists)
744
+ await walletManager.resolve(params.walletId);
745
+
746
+ // Get spoke provider for source chain
747
+ const spokeProvider = await getSpokeProvider(
748
+ params.walletId,
749
+ params.srcChainId
750
+ );
751
+
752
+ // Construct intent object for cancellation
753
+ const intent = {
754
+ id: params.intent.id,
755
+ srcChainId: params.intent.srcChainId,
756
+ dstChainId: params.intent.dstChainId,
757
+ srcToken: params.intent.srcToken,
758
+ dstToken: params.intent.dstToken,
759
+ amount: params.intent.amount,
760
+ deadline: BigInt(params.intent.deadline),
761
+ createdAt: Date.now(),
762
+ status: 'pending'
763
+ } as unknown as Intent;
764
+
765
+ // Cancel the intent - SDK expects (intent, spokeProvider)
766
+ const cancelResult = await (sodaxClient as any).swaps.cancelIntent(intent, spokeProvider);
767
+
768
+ // Handle Result type
769
+ if (cancelResult.ok === false) {
770
+ throw new Error(`Cancel failed: ${serializeError(cancelResult.error)}`);
771
+ }
772
+
773
+ const cancelTx = cancelResult.ok ? cancelResult.value : cancelResult;
774
+ const cancelTxHash = typeof cancelTx === 'string' ? cancelTx : String(cancelTx);
775
+
776
+ // Wait for cancellation confirmation if possible
777
+ // SDK may expose waitForTransactionReceipt on the underlying wallet provider
778
+ if ((spokeProvider as any).walletProvider?.waitForTransactionReceipt) {
779
+ await (spokeProvider as any).walletProvider.waitForTransactionReceipt(cancelTxHash);
780
+ }
781
+
782
+ const result = {
783
+ success: true,
784
+ txHash: cancelTxHash,
785
+ message: 'Intent cancelled successfully'
786
+ };
787
+
788
+ logStructured({
789
+ requestId,
790
+ opType: 'swap_cancel',
791
+ walletId: params.walletId,
792
+ chainId: params.srcChainId,
793
+ intentId: params.intent.id,
794
+ txHash: cancelTxHash,
795
+ durationMs: Date.now() - startTime,
796
+ success: true
797
+ });
798
+
799
+ return result;
800
+ } catch (error) {
801
+ logStructured({
802
+ requestId,
803
+ opType: 'swap_cancel',
804
+ walletId: params.walletId,
805
+ chainId: params.srcChainId,
806
+ intentId: params.intent.id,
807
+ durationMs: Date.now() - startTime,
808
+ success: false,
809
+ error: error instanceof Error ? error.message : String(error)
810
+ });
811
+
812
+ throw new Error(`Failed to cancel swap: ${error instanceof Error ? error.message : String(error)}`);
813
+ }
814
+ }
815
+
816
+ // ============================================================================
817
+ // Utility Functions
818
+ // ============================================================================
819
+
820
+ function generateRequestId(): string {
821
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
822
+ }
823
+
824
+ function calculateDeadline(secondsFromNow: number): number {
825
+ return Math.floor(Date.now() / 1000) + secondsFromNow;
826
+ }
827
+
828
+ interface LogEntry {
829
+ requestId: string;
830
+ opType: string;
831
+ walletId?: string;
832
+ chainId?: string;
833
+ chainIds?: string[];
834
+ token?: string;
835
+ tokenAddresses?: string[];
836
+ intentHash?: string;
837
+ txHash?: string;
838
+ spokeTxHash?: string;
839
+ hubTxHash?: string;
840
+ approvalTx?: string;
841
+ status?: string;
842
+ durationMs?: number;
843
+ success?: boolean;
844
+ error?: string;
845
+ message?: string;
846
+ intentId?: string;
847
+ }
848
+
849
+ function logStructured(entry: LogEntry): void {
850
+ // Structured JSON logging for observability
851
+ // Use replacer to handle BigInt serialization
852
+ console.log(JSON.stringify({
853
+ ...entry,
854
+ timestamp: new Date().toISOString(),
855
+ component: 'amped-defi-swap'
856
+ }, (k, v) => typeof v === 'bigint' ? v.toString() : v));
857
+ }
858
+
859
+ // ============================================================================
860
+ // Tool Registration
861
+ // ============================================================================
862
+
863
+ export function registerSwapTools(agentTools: AgentTools): void {
864
+ // Register swap quote tool
865
+ agentTools.register({
866
+ name: 'amped_swap_quote',
867
+ summary: 'Get a swap quote for exact-in or exact-out swaps across chains',
868
+ description: 'Retrieves a quote for swapping tokens across chains using the SODAX swap protocol. ' +
869
+ 'Supports both exact input (specify input amount, get output estimate) and ' +
870
+ 'exact output (specify desired output, get required input) modes.',
871
+ schema: SwapQuoteRequestSchema,
872
+ handler: handleSwapQuote
873
+ });
874
+
875
+ // Register swap execute tool
876
+ agentTools.register({
877
+ name: 'amped_swap_execute',
878
+ summary: 'Execute a swap with policy enforcement and allowance handling',
879
+ description: 'Executes a swap using a previously obtained quote. ' +
880
+ 'Performs policy checks, validates allowances, approves tokens if needed, ' +
881
+ 'and executes the swap transaction. Returns transaction hashes and intent status.',
882
+ schema: SwapExecuteParamsSchema,
883
+ handler: handleSwapExecute
884
+ });
885
+
886
+ // Register swap status tool
887
+ agentTools.register({
888
+ name: 'amped_swap_status',
889
+ summary: 'Check the status of a swap intent or transaction',
890
+ description: 'Polls the status of a swap by intent hash or transaction hash. ' +
891
+ 'Returns current status, fill amount, error details if failed, and timing information.',
892
+ schema: SwapStatusParamsSchema,
893
+ handler: handleSwapStatus
894
+ });
895
+
896
+ // Register swap cancel tool
897
+ agentTools.register({
898
+ name: 'amped_swap_cancel',
899
+ summary: 'Cancel an active swap intent',
900
+ description: 'Cancels a pending swap intent on the source chain. ' +
901
+ 'Requires the intent details and source chain ID. Returns cancellation transaction hash.',
902
+ schema: SwapCancelParamsSchema,
903
+ handler: handleSwapCancel
904
+ });
905
+ }
906
+
907
+ // Silence unused variable warnings for result schemas (used for documentation)
908
+ void SwapExecuteResultSchema;
909
+ void SwapStatusResultSchema;
910
+ void SwapCancelResultSchema;
911
+
912
+ // Export schemas with consistent naming
913
+ export {
914
+ SwapQuoteRequestSchema as SwapQuoteSchema,
915
+ SwapExecuteParamsSchema as SwapExecuteSchema,
916
+ SwapStatusParamsSchema as SwapStatusSchema,
917
+ SwapCancelParamsSchema as SwapCancelSchema,
918
+ };
919
+
920
+ // Export handlers
921
+ export {
922
+ handleSwapQuote,
923
+ handleSwapExecute,
924
+ handleSwapStatus,
925
+ handleSwapCancel,
926
+ };