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,389 @@
1
+ /**
2
+ * Policy Engine
3
+ *
4
+ * Enforces security policies for DeFi operations including:
5
+ * - Spend limits per transaction and daily
6
+ * - Allowed chain and token allowlists
7
+ * - Blocked recipient addresses
8
+ * - Maximum slippage tolerance
9
+ * - Simulation requirements
10
+ */
11
+
12
+ import { BridgeOperation, PolicyConfig } from '../types';
13
+ import { normalizeChainId } from '../wallet/types';
14
+
15
+ /**
16
+ * Policy check result
17
+ */
18
+ export interface PolicyCheckResult {
19
+ allowed: boolean;
20
+ reason?: string;
21
+ details?: Record<string, unknown>;
22
+ }
23
+
24
+ /**
25
+ * Policy Engine class for enforcing security constraints
26
+ */
27
+ export class PolicyEngine {
28
+ private config: PolicyConfig;
29
+
30
+ constructor(policyId?: string) {
31
+ this.config = this.loadPolicyConfig(policyId);
32
+ }
33
+
34
+ /**
35
+ * Load policy configuration from environment
36
+ *
37
+ * @param policyId - Optional policy profile ID for custom limits
38
+ * @returns The policy configuration
39
+ */
40
+ private loadPolicyConfig(policyId?: string): PolicyConfig {
41
+ const limitsJson = process.env.AMPED_OC_LIMITS_JSON;
42
+
43
+ if (!limitsJson) {
44
+ return {};
45
+ }
46
+
47
+ try {
48
+ const allConfigs = JSON.parse(limitsJson) as Record<string, PolicyConfig>;
49
+
50
+ // If policyId is specified, use that config; otherwise use 'default' or empty
51
+ const config = policyId
52
+ ? allConfigs[policyId]
53
+ : allConfigs['default'] || allConfigs;
54
+
55
+ if (policyId && !config) {
56
+ return allConfigs['default'] || {};
57
+ }
58
+
59
+ return config || {};
60
+ } catch (_error) {
61
+ return {};
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Check if a chain is allowed
67
+ *
68
+ * @param chainId - The chain ID to check
69
+ * @returns Policy check result
70
+ */
71
+ private checkChainAllowed(chainId: string): PolicyCheckResult {
72
+ const { allowedChains } = this.config;
73
+
74
+ if (allowedChains && allowedChains.length > 0) {
75
+ const normalizedChain = normalizeChainId(chainId);
76
+ if (!allowedChains.includes(normalizedChain)) {
77
+ return {
78
+ allowed: false,
79
+ reason: `Chain not allowed: ${chainId}. Allowed chains: ${allowedChains.join(', ')}`,
80
+ };
81
+ }
82
+ }
83
+
84
+ return { allowed: true };
85
+ }
86
+
87
+ /**
88
+ * Check if a token is allowed on a specific chain
89
+ *
90
+ * @param chainId - The chain ID
91
+ * @param token - The token address or symbol
92
+ * @returns Policy check result
93
+ */
94
+ private checkTokenAllowed(chainId: string, token: string): PolicyCheckResult {
95
+ const { allowedTokensByChain } = this.config;
96
+
97
+ if (allowedTokensByChain) {
98
+ const normalizedChainForTokens = normalizeChainId(chainId);
99
+ const allowedTokens = allowedTokensByChain[normalizedChainForTokens];
100
+ if (allowedTokens && allowedTokens.length > 0) {
101
+ if (!allowedTokens.includes(token)) {
102
+ return {
103
+ allowed: false,
104
+ reason: `Token not allowed on ${chainId}: ${token}. Allowed tokens: ${allowedTokens.join(', ')}`,
105
+ };
106
+ }
107
+ }
108
+ }
109
+
110
+ return { allowed: true };
111
+ }
112
+
113
+ /**
114
+ * Check if a recipient is blocked
115
+ *
116
+ * @param recipient - The recipient address
117
+ * @returns Policy check result
118
+ */
119
+ private checkRecipientNotBlocked(recipient: string): PolicyCheckResult {
120
+ const { blockedRecipients } = this.config;
121
+
122
+ if (blockedRecipients && blockedRecipients.length > 0) {
123
+ if (blockedRecipients.includes(recipient.toLowerCase())) {
124
+ return {
125
+ allowed: false,
126
+ reason: `Recipient is blocked: ${recipient}`,
127
+ };
128
+ }
129
+ }
130
+
131
+ return { allowed: true };
132
+ }
133
+
134
+ /**
135
+ * Check bridge amount against limits
136
+ *
137
+ * @param token - The token address or symbol
138
+ * @param amount - The amount in human-readable units
139
+ * @returns Policy check result
140
+ */
141
+ private checkBridgeAmount(token: string, amount: string): PolicyCheckResult {
142
+ const { maxBridgeAmountToken } = this.config;
143
+
144
+ if (maxBridgeAmountToken) {
145
+ const maxAmount = maxBridgeAmountToken[token];
146
+ if (maxAmount !== undefined) {
147
+ const amountNum = parseFloat(amount);
148
+ if (amountNum > maxAmount) {
149
+ return {
150
+ allowed: false,
151
+ reason: `Bridge amount ${amount} exceeds maximum ${maxAmount} for token ${token}`,
152
+ details: { maxAllowed: maxAmount, requested: amountNum },
153
+ };
154
+ }
155
+ }
156
+ }
157
+
158
+ return { allowed: true };
159
+ }
160
+
161
+ /**
162
+ * Check a bridge operation against all policies
163
+ *
164
+ * @param operation - The bridge operation to validate
165
+ * @returns Policy check result
166
+ */
167
+ async checkBridge(operation: BridgeOperation): Promise<PolicyCheckResult> {
168
+ const { srcChainId, dstChainId, srcToken, dstToken, amount, recipient } = operation;
169
+
170
+ // Check source chain
171
+ const srcChainCheck = this.checkChainAllowed(srcChainId);
172
+ if (!srcChainCheck.allowed) return srcChainCheck;
173
+
174
+ // Check destination chain
175
+ const dstChainCheck = this.checkChainAllowed(dstChainId);
176
+ if (!dstChainCheck.allowed) return dstChainCheck;
177
+
178
+ // Check source token
179
+ const srcTokenCheck = this.checkTokenAllowed(srcChainId, srcToken);
180
+ if (!srcTokenCheck.allowed) return srcTokenCheck;
181
+
182
+ // Check destination token
183
+ const dstTokenCheck = this.checkTokenAllowed(dstChainId, dstToken);
184
+ if (!dstTokenCheck.allowed) return dstTokenCheck;
185
+
186
+ // Check amount limits
187
+ const amountCheck = this.checkBridgeAmount(srcToken, amount);
188
+ if (!amountCheck.allowed) return amountCheck;
189
+
190
+ // Check recipient if specified
191
+ if (recipient) {
192
+ const recipientCheck = this.checkRecipientNotBlocked(recipient);
193
+ if (!recipientCheck.allowed) return recipientCheck;
194
+ }
195
+
196
+ return { allowed: true };
197
+ }
198
+
199
+ /**
200
+ * Get the current policy configuration
201
+ * @returns The policy configuration
202
+ */
203
+ getConfig(): PolicyConfig {
204
+ return { ...this.config };
205
+ }
206
+
207
+ /**
208
+ * Get available policy IDs from the configuration
209
+ * @returns Array of available policy IDs
210
+ */
211
+ getAvailablePolicies(): string[] {
212
+ const limitsJson = process.env.AMPED_OC_LIMITS_JSON;
213
+ if (!limitsJson) return [];
214
+
215
+ try {
216
+ const allConfigs = JSON.parse(limitsJson) as Record<string, PolicyConfig>;
217
+ return Object.keys(allConfigs);
218
+ } catch {
219
+ return [];
220
+ }
221
+ }
222
+
223
+ // ============================================================================
224
+ // Swap Policy Checks
225
+ // ============================================================================
226
+
227
+ /**
228
+ * Check swap input amount against USD limits
229
+ */
230
+ private checkSwapAmount(inputAmount: string, srcToken: string): PolicyCheckResult {
231
+ const { maxSwapInputUsd, maxSwapInputToken } = this.config;
232
+
233
+ // Check per-token limit if configured
234
+ if (maxSwapInputToken) {
235
+ const maxTokenAmount = maxSwapInputToken[srcToken];
236
+ if (maxTokenAmount !== undefined) {
237
+ const amountNum = parseFloat(inputAmount);
238
+ if (amountNum > maxTokenAmount) {
239
+ return {
240
+ allowed: false,
241
+ reason: `Swap input amount ${inputAmount} exceeds maximum ${maxTokenAmount} for token ${srcToken}`,
242
+ details: { maxAllowed: maxTokenAmount, requested: amountNum },
243
+ };
244
+ }
245
+ }
246
+ }
247
+
248
+ // Note: USD limit check would require price oracle integration
249
+ // For now, we skip enforcement without prices
250
+
251
+ return { allowed: true };
252
+ }
253
+
254
+ /**
255
+ * Check slippage against maximum allowed
256
+ */
257
+ private checkSlippage(slippageBps: number): PolicyCheckResult {
258
+ const { maxSlippageBps } = this.config;
259
+
260
+ if (maxSlippageBps !== undefined && slippageBps > maxSlippageBps) {
261
+ return {
262
+ allowed: false,
263
+ reason: `Slippage ${slippageBps} bps exceeds maximum allowed ${maxSlippageBps} bps`,
264
+ details: { maxAllowed: maxSlippageBps, requested: slippageBps },
265
+ };
266
+ }
267
+
268
+ return { allowed: true };
269
+ }
270
+
271
+ /**
272
+ * Check a swap operation against all policies
273
+ */
274
+ async checkSwap(params: {
275
+ walletId: string;
276
+ srcChainId: string;
277
+ dstChainId: string;
278
+ srcToken: string;
279
+ dstToken: string;
280
+ inputAmount: string;
281
+ slippageBps: number;
282
+ policyId?: string;
283
+ }): Promise<PolicyCheckResult> {
284
+ const { srcChainId, dstChainId, srcToken, dstToken, inputAmount, slippageBps } = params;
285
+
286
+ // Check source chain
287
+ const srcChainCheck = this.checkChainAllowed(srcChainId);
288
+ if (!srcChainCheck.allowed) return srcChainCheck;
289
+
290
+ // Check destination chain
291
+ const dstChainCheck = this.checkChainAllowed(dstChainId);
292
+ if (!dstChainCheck.allowed) return dstChainCheck;
293
+
294
+ // Check source token
295
+ const srcTokenCheck = this.checkTokenAllowed(srcChainId, srcToken);
296
+ if (!srcTokenCheck.allowed) return srcTokenCheck;
297
+
298
+ // Check destination token
299
+ const dstTokenCheck = this.checkTokenAllowed(dstChainId, dstToken);
300
+ if (!dstTokenCheck.allowed) return dstTokenCheck;
301
+
302
+ // Check swap amount limits
303
+ const amountCheck = this.checkSwapAmount(inputAmount, srcToken);
304
+ if (!amountCheck.allowed) return amountCheck;
305
+
306
+ // Check slippage
307
+ const slippageCheck = this.checkSlippage(slippageBps);
308
+ if (!slippageCheck.allowed) return slippageCheck;
309
+
310
+ return { allowed: true };
311
+ }
312
+
313
+ // ============================================================================
314
+ // Money Market Policy Checks
315
+ // ============================================================================
316
+
317
+ /**
318
+ * Check borrow amount against limits
319
+ */
320
+ private checkBorrowAmount(token: string, amount: string, amountUsd?: number): PolicyCheckResult {
321
+ const { maxBorrowUsd, maxBorrowToken } = this.config;
322
+
323
+ // Check per-token limit if configured
324
+ if (maxBorrowToken) {
325
+ const maxTokenAmount = maxBorrowToken[token];
326
+ if (maxTokenAmount !== undefined) {
327
+ const amountNum = parseFloat(amount);
328
+ if (amountNum > maxTokenAmount) {
329
+ return {
330
+ allowed: false,
331
+ reason: `Borrow amount ${amount} exceeds maximum ${maxTokenAmount} for token ${token}`,
332
+ details: { maxAllowed: maxTokenAmount, requested: amountNum },
333
+ };
334
+ }
335
+ }
336
+ }
337
+
338
+ // Check USD limit if amountUsd is provided
339
+ if (maxBorrowUsd !== undefined && amountUsd !== undefined) {
340
+ if (amountUsd > maxBorrowUsd) {
341
+ return {
342
+ allowed: false,
343
+ reason: `Borrow amount $${amountUsd} exceeds maximum $${maxBorrowUsd}`,
344
+ details: { maxAllowed: maxBorrowUsd, requested: amountUsd },
345
+ };
346
+ }
347
+ }
348
+
349
+ return { allowed: true };
350
+ }
351
+
352
+ /**
353
+ * Check a money market operation against all policies
354
+ */
355
+ async checkMoneyMarket(params: {
356
+ walletId: string;
357
+ chainId: string;
358
+ dstChainId?: string;
359
+ token: string;
360
+ amount: string;
361
+ amountUsd?: number;
362
+ operation: 'supply' | 'withdraw' | 'borrow' | 'repay';
363
+ policyId?: string;
364
+ }): Promise<PolicyCheckResult> {
365
+ const { chainId, dstChainId, token, amount, amountUsd, operation } = params;
366
+
367
+ // Check source chain
368
+ const chainCheck = this.checkChainAllowed(chainId);
369
+ if (!chainCheck.allowed) return chainCheck;
370
+
371
+ // Check destination chain if cross-chain operation
372
+ if (dstChainId) {
373
+ const dstChainCheck = this.checkChainAllowed(dstChainId);
374
+ if (!dstChainCheck.allowed) return dstChainCheck;
375
+ }
376
+
377
+ // Check token
378
+ const tokenCheck = this.checkTokenAllowed(chainId, token);
379
+ if (!tokenCheck.allowed) return tokenCheck;
380
+
381
+ // Operation-specific checks
382
+ if (operation === 'borrow') {
383
+ const borrowCheck = this.checkBorrowAmount(token, amount, amountUsd);
384
+ if (!borrowCheck.allowed) return borrowCheck;
385
+ }
386
+
387
+ return { allowed: true };
388
+ }
389
+ }
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Spoke Provider Factory
3
+ *
4
+ * Creates spoke providers for SODAX operations.
5
+ * Supports both local key signing and Bankr API execution.
6
+ *
7
+ * Flow:
8
+ * 1. Resolve wallet by nickname using WalletManager
9
+ * 2. Check if wallet supports requested chain
10
+ * 3. For local wallets: use SDK's EvmWalletProvider
11
+ * 4. For Bankr wallets: use BankrWalletProvider (submits to Bankr API)
12
+ */
13
+
14
+ // Official SDK wallet provider
15
+ import { EvmWalletProvider } from '@sodax/wallet-sdk-core';
16
+
17
+ // Import spoke providers and chain config from SDK
18
+ import {
19
+ EvmSpokeProvider,
20
+ SonicSpokeProvider,
21
+ type SpokeProvider
22
+ } from '@sodax/sdk';
23
+
24
+ // Import chain configuration from types
25
+ import { spokeChainConfig, type SpokeChainId } from '@sodax/types';
26
+
27
+ // Import wallet management
28
+ import { getWalletManager, type IWalletBackend, createBankrWalletProvider } from '../wallet';
29
+ import { getWalletAdapter } from '../wallet/skillWalletAdapter';
30
+ import { BANKR_CHAIN_IDS, normalizeChainId, getBankrChainId } from '../wallet/types';
31
+
32
+ // Cache for providers: Map<cacheKey, SpokeProvider>
33
+ const providerCache = new Map<string, SpokeProvider>();
34
+
35
+ // Sonic hub chain identifier
36
+ const SONIC_CHAIN_ID = 'sonic';
37
+
38
+ // Chain ID mapping for SDK (some chains need specific format)
39
+ const CHAIN_ID_MAP: Record<string, SpokeChainId> = {
40
+ 'sonic': 'sonic',
41
+ 'ethereum': 'ethereum',
42
+ 'arbitrum': '0xa4b1.arbitrum',
43
+ 'optimism': '0xa.optimism',
44
+ 'base': '0x2105.base',
45
+ 'polygon': '0x89.polygon',
46
+ 'bsc': '0x38.bsc',
47
+ 'avalanche': '0xa86a.avax',
48
+ 'lightlink': 'lightlink',
49
+ 'hyperevm': 'hyper',
50
+ 'hyper': 'hyper',
51
+ } as Record<string, SpokeChainId>;
52
+
53
+ /**
54
+ * Get RPC URL for a chain
55
+ */
56
+ async function getRpcUrl(chainId: string): Promise<string> {
57
+ const skillAdapter = getWalletAdapter();
58
+ return skillAdapter.getRpcUrl(chainId);
59
+ }
60
+
61
+ /**
62
+ * Get the SDK chain ID for a given chain
63
+ */
64
+ function getSdkChainId(chainId: string): SpokeChainId {
65
+ return (CHAIN_ID_MAP[chainId] || chainId) as SpokeChainId;
66
+ }
67
+
68
+ /**
69
+ * Validate that wallet supports the requested chain
70
+ */
71
+ function validateChainSupport(wallet: IWalletBackend, chainId: string): void {
72
+ const normalizedForWallet = normalizeChainId(chainId);
73
+ if (!wallet.supportsChain(normalizedForWallet)) {
74
+ throw new Error(
75
+ `Wallet "${wallet.nickname}" doesn't support chain "${chainId}". ` +
76
+ `Supported chains: ${wallet.supportedChains.join(', ')}. ` +
77
+ `Try a different wallet.`
78
+ );
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Create a spoke provider for local key signing
84
+ */
85
+ async function createLocalSpokeProvider(
86
+ wallet: IWalletBackend,
87
+ chainId: string,
88
+ rpcUrl: string
89
+ ): Promise<SpokeProvider> {
90
+ if (!wallet.getPrivateKey) {
91
+ throw new Error(`Wallet "${wallet.nickname}" does not support local signing`);
92
+ }
93
+
94
+ const privateKey = await wallet.getPrivateKey();
95
+ const sdkChainId = getSdkChainId(chainId);
96
+
97
+ // Get chain config from SDK
98
+ const chainConfig = spokeChainConfig[sdkChainId];
99
+ if (!chainConfig) {
100
+ throw new Error(`Chain config not found for: ${sdkChainId}. Available: ${Object.keys(spokeChainConfig).join(', ')}`);
101
+ }
102
+
103
+ // Create wallet provider using official SDK
104
+ const walletProvider = new EvmWalletProvider({
105
+ privateKey,
106
+ chainId: sdkChainId,
107
+ rpcUrl: rpcUrl as `http${string}`,
108
+ });
109
+
110
+ // Use SonicSpokeProvider for Sonic hub chain, EvmSpokeProvider for others
111
+ if (chainId === SONIC_CHAIN_ID) {
112
+ console.log('[spokeProviderFactory] Creating SonicSpokeProvider', {
113
+ wallet: wallet.nickname,
114
+ chainId,
115
+ });
116
+
117
+ return new SonicSpokeProvider(
118
+ walletProvider,
119
+ chainConfig as any,
120
+ rpcUrl
121
+ );
122
+ } else {
123
+ console.log('[spokeProviderFactory] Creating EvmSpokeProvider', {
124
+ wallet: wallet.nickname,
125
+ chainId,
126
+ sdkChainId,
127
+ });
128
+
129
+ return new EvmSpokeProvider(
130
+ walletProvider,
131
+ chainConfig as any,
132
+ rpcUrl
133
+ );
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Create a spoke provider for Bankr wallet
139
+ * Uses BankrWalletProvider which submits transactions to Bankr API
140
+ */
141
+ async function createBankrSpokeProvider(
142
+ wallet: IWalletBackend,
143
+ chainId: string,
144
+ rpcUrl: string
145
+ ): Promise<SpokeProvider> {
146
+ const sdkChainId = getSdkChainId(chainId);
147
+
148
+ // Normalize chain ID for Bankr lookup (0x2105.base -> base)
149
+ const normalizedChainId = normalizeChainId(chainId);
150
+ const numericChainId = getBankrChainId(normalizedChainId);
151
+
152
+ console.log('[spokeProviderFactory] Bankr chain resolution', {
153
+ input: chainId,
154
+ normalized: normalizedChainId,
155
+ numeric: numericChainId,
156
+ });
157
+
158
+ // Get chain config from SDK
159
+ const chainConfig = spokeChainConfig[sdkChainId];
160
+ if (!chainConfig) {
161
+ throw new Error(`Chain config not found for: ${sdkChainId}`);
162
+ }
163
+
164
+ // Get Bankr API key from environment
165
+ const apiKey = process.env.BANKR_API_KEY;
166
+ if (!apiKey) {
167
+ throw new Error('BANKR_API_KEY environment variable not set');
168
+ }
169
+
170
+ // Get the Bankr wallet address (cached after first call)
171
+ const walletAddress = await wallet.getAddress();
172
+
173
+ // Create BankrWalletProvider which implements IEvmWalletProvider
174
+ const walletProvider = createBankrWalletProvider({
175
+ apiKey,
176
+ apiUrl: process.env.BANKR_API_URL,
177
+ chainId: numericChainId,
178
+ rpcUrl,
179
+ cachedAddress: walletAddress,
180
+ });
181
+
182
+ console.log('[spokeProviderFactory] Creating EvmSpokeProvider with Bankr backend', {
183
+ wallet: wallet.nickname,
184
+ chainId,
185
+ sdkChainId,
186
+ address: walletAddress?.slice(0, 10) + '...',
187
+ });
188
+
189
+ // Use standard EvmSpokeProvider with our BankrWalletProvider
190
+ // The SDK doesn't care how transactions are signed - it just calls the interface methods
191
+ return new EvmSpokeProvider(
192
+ walletProvider as any, // BankrWalletProvider implements IEvmWalletProvider
193
+ chainConfig as any,
194
+ rpcUrl
195
+ );
196
+ }
197
+
198
+ /**
199
+ * Create a spoke provider for the given wallet and chain
200
+ *
201
+ * @param walletId - Wallet nickname (e.g., "main", "bankr", "trading")
202
+ * @param chainId - Chain identifier (e.g., "ethereum", "base")
203
+ */
204
+ async function createSpokeProvider(
205
+ walletId: string,
206
+ chainId: string
207
+ ): Promise<SpokeProvider> {
208
+ // Get wallet from unified manager
209
+ const walletManager = getWalletManager();
210
+ const wallet = await walletManager.resolve(walletId);
211
+
212
+ // Validate chain support
213
+ validateChainSupport(wallet, chainId);
214
+
215
+ const rpcUrl = await getRpcUrl(chainId);
216
+
217
+ // Route based on wallet type
218
+ if (wallet.type === 'bankr') {
219
+ // Use BankrWalletProvider for Bankr wallets
220
+ return createBankrSpokeProvider(wallet, chainId, rpcUrl);
221
+ }
222
+
223
+ // Local key signing (evm-wallet-skill or env)
224
+ return createLocalSpokeProvider(wallet, chainId, rpcUrl);
225
+ }
226
+
227
+ /**
228
+ * Get a spoke provider for the given wallet and chain
229
+ * Returns cached provider if available, otherwise creates a new one
230
+ *
231
+ * @param walletId - The wallet identifier/nickname
232
+ * @param chainId - The chain identifier
233
+ * @param raw - If true, still creates full provider (raw mode not yet supported)
234
+ * @returns The spoke provider instance
235
+ */
236
+ export async function getSpokeProvider(
237
+ walletId: string,
238
+ chainId: string,
239
+ raw = false
240
+ ): Promise<SpokeProvider> {
241
+ const cacheKey = `${walletId}:${chainId}`;
242
+
243
+ // Check cache
244
+ const cached = providerCache.get(cacheKey);
245
+ if (cached) {
246
+ console.log('[spokeProviderFactory] Using cached provider', {
247
+ walletId,
248
+ chainId,
249
+ });
250
+ return cached;
251
+ }
252
+
253
+ // Create new provider
254
+ const provider = await createSpokeProvider(walletId, chainId);
255
+
256
+ // Cache the provider
257
+ providerCache.set(cacheKey, provider);
258
+
259
+ return provider;
260
+ }
261
+
262
+ /**
263
+ * Clear the provider cache
264
+ * Useful for testing or when wallet configuration changes
265
+ */
266
+ export function clearProviderCache(): void {
267
+ providerCache.clear();
268
+ console.log('[spokeProviderFactory] Provider cache cleared');
269
+ }
270
+
271
+ /**
272
+ * Get cache statistics
273
+ * @returns Object with cache size and keys
274
+ */
275
+ export function getCacheStats(): { size: number; keys: string[] } {
276
+ return {
277
+ size: providerCache.size,
278
+ keys: Array.from(providerCache.keys()),
279
+ };
280
+ }
281
+
282
+ // Export the type for use in other modules
283
+ export type { SpokeProvider };