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