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.
- package/README.md +757 -0
- package/dist/__mocks__/@sodax/sdk.d.ts +24 -0
- package/dist/__mocks__/@sodax/sdk.d.ts.map +1 -0
- package/dist/__mocks__/@sodax/sdk.js +24 -0
- package/dist/__mocks__/@sodax/sdk.js.map +1 -0
- package/dist/__tests__/setup.d.ts +4 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +32 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +281 -0
- package/dist/index.js.map +1 -0
- package/dist/policy/policyEngine.d.ts +119 -0
- package/dist/policy/policyEngine.d.ts.map +1 -0
- package/dist/policy/policyEngine.js +322 -0
- package/dist/policy/policyEngine.js.map +1 -0
- package/dist/providers/spokeProviderFactory.d.ts +38 -0
- package/dist/providers/spokeProviderFactory.d.ts.map +1 -0
- package/dist/providers/spokeProviderFactory.js +212 -0
- package/dist/providers/spokeProviderFactory.js.map +1 -0
- package/dist/sodax/client.d.ts +34 -0
- package/dist/sodax/client.d.ts.map +1 -0
- package/dist/sodax/client.js +99 -0
- package/dist/sodax/client.js.map +1 -0
- package/dist/tools/bridge.d.ts +105 -0
- package/dist/tools/bridge.d.ts.map +1 -0
- package/dist/tools/bridge.js +334 -0
- package/dist/tools/bridge.js.map +1 -0
- package/dist/tools/discovery.d.ts +141 -0
- package/dist/tools/discovery.d.ts.map +1 -0
- package/dist/tools/discovery.js +777 -0
- package/dist/tools/discovery.js.map +1 -0
- package/dist/tools/moneyMarket.d.ts +227 -0
- package/dist/tools/moneyMarket.d.ts.map +1 -0
- package/dist/tools/moneyMarket.js +867 -0
- package/dist/tools/moneyMarket.js.map +1 -0
- package/dist/tools/portfolio.d.ts +43 -0
- package/dist/tools/portfolio.d.ts.map +1 -0
- package/dist/tools/portfolio.js +538 -0
- package/dist/tools/portfolio.js.map +1 -0
- package/dist/tools/swap.d.ts +71 -0
- package/dist/tools/swap.d.ts.map +1 -0
- package/dist/tools/swap.js +762 -0
- package/dist/tools/swap.js.map +1 -0
- package/dist/tools/walletManagement.d.ts +80 -0
- package/dist/tools/walletManagement.d.ts.map +1 -0
- package/dist/tools/walletManagement.js +289 -0
- package/dist/tools/walletManagement.js.map +1 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/errorUtils.d.ts +2 -0
- package/dist/utils/errorUtils.d.ts.map +1 -0
- package/dist/utils/errorUtils.js +19 -0
- package/dist/utils/errorUtils.js.map +1 -0
- package/dist/utils/errors.d.ts +144 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +310 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/positionAggregator.d.ts +122 -0
- package/dist/utils/positionAggregator.d.ts.map +1 -0
- package/dist/utils/positionAggregator.js +377 -0
- package/dist/utils/positionAggregator.js.map +1 -0
- package/dist/utils/priceService.d.ts +45 -0
- package/dist/utils/priceService.d.ts.map +1 -0
- package/dist/utils/priceService.js +108 -0
- package/dist/utils/priceService.js.map +1 -0
- package/dist/utils/sodaxApi.d.ts +92 -0
- package/dist/utils/sodaxApi.d.ts.map +1 -0
- package/dist/utils/sodaxApi.js +143 -0
- package/dist/utils/sodaxApi.js.map +1 -0
- package/dist/utils/tokenResolver.d.ts +54 -0
- package/dist/utils/tokenResolver.d.ts.map +1 -0
- package/dist/utils/tokenResolver.js +252 -0
- package/dist/utils/tokenResolver.js.map +1 -0
- package/dist/wallet/backendConfig.d.ts +37 -0
- package/dist/wallet/backendConfig.d.ts.map +1 -0
- package/dist/wallet/backendConfig.js +125 -0
- package/dist/wallet/backendConfig.js.map +1 -0
- package/dist/wallet/backends/BankrBackend.d.ts +73 -0
- package/dist/wallet/backends/BankrBackend.d.ts.map +1 -0
- package/dist/wallet/backends/BankrBackend.js +315 -0
- package/dist/wallet/backends/BankrBackend.js.map +1 -0
- package/dist/wallet/backends/BankrWalletProvider.d.ts +75 -0
- package/dist/wallet/backends/BankrWalletProvider.d.ts.map +1 -0
- package/dist/wallet/backends/BankrWalletProvider.js +243 -0
- package/dist/wallet/backends/BankrWalletProvider.js.map +1 -0
- package/dist/wallet/backends/EnvBackend.d.ts +50 -0
- package/dist/wallet/backends/EnvBackend.d.ts.map +1 -0
- package/dist/wallet/backends/EnvBackend.js +114 -0
- package/dist/wallet/backends/EnvBackend.js.map +1 -0
- package/dist/wallet/backends/EvmWalletSkillBackend.d.ts +40 -0
- package/dist/wallet/backends/EvmWalletSkillBackend.d.ts.map +1 -0
- package/dist/wallet/backends/EvmWalletSkillBackend.js +81 -0
- package/dist/wallet/backends/EvmWalletSkillBackend.js.map +1 -0
- package/dist/wallet/backends/index.d.ts +10 -0
- package/dist/wallet/backends/index.d.ts.map +1 -0
- package/dist/wallet/backends/index.js +10 -0
- package/dist/wallet/backends/index.js.map +1 -0
- package/dist/wallet/index.d.ts +9 -0
- package/dist/wallet/index.d.ts.map +1 -0
- package/dist/wallet/index.js +12 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/providers/AmpedWalletProvider.d.ts +107 -0
- package/dist/wallet/providers/AmpedWalletProvider.d.ts.map +1 -0
- package/dist/wallet/providers/AmpedWalletProvider.js +208 -0
- package/dist/wallet/providers/AmpedWalletProvider.js.map +1 -0
- package/dist/wallet/providers/BankrBackend.d.ts +105 -0
- package/dist/wallet/providers/BankrBackend.d.ts.map +1 -0
- package/dist/wallet/providers/BankrBackend.js +327 -0
- package/dist/wallet/providers/BankrBackend.js.map +1 -0
- package/dist/wallet/providers/LocalKeyBackend.d.ts +62 -0
- package/dist/wallet/providers/LocalKeyBackend.d.ts.map +1 -0
- package/dist/wallet/providers/LocalKeyBackend.js +152 -0
- package/dist/wallet/providers/LocalKeyBackend.js.map +1 -0
- package/dist/wallet/providers/chainConfig.d.ts +209 -0
- package/dist/wallet/providers/chainConfig.d.ts.map +1 -0
- package/dist/wallet/providers/chainConfig.js +175 -0
- package/dist/wallet/providers/chainConfig.js.map +1 -0
- package/dist/wallet/providers/index.d.ts +30 -0
- package/dist/wallet/providers/index.d.ts.map +1 -0
- package/dist/wallet/providers/index.js +32 -0
- package/dist/wallet/providers/index.js.map +1 -0
- package/dist/wallet/providers/types.d.ts +156 -0
- package/dist/wallet/providers/types.d.ts.map +1 -0
- package/dist/wallet/providers/types.js +11 -0
- package/dist/wallet/providers/types.js.map +1 -0
- package/dist/wallet/skillWalletAdapter.d.ts +96 -0
- package/dist/wallet/skillWalletAdapter.d.ts.map +1 -0
- package/dist/wallet/skillWalletAdapter.js +280 -0
- package/dist/wallet/skillWalletAdapter.js.map +1 -0
- package/dist/wallet/types.d.ts +134 -0
- package/dist/wallet/types.d.ts.map +1 -0
- package/dist/wallet/types.js +138 -0
- package/dist/wallet/types.js.map +1 -0
- package/dist/wallet/walletManager.d.ts +111 -0
- package/dist/wallet/walletManager.d.ts.map +1 -0
- package/dist/wallet/walletManager.js +476 -0
- package/dist/wallet/walletManager.js.map +1 -0
- package/dist/wallet/walletRegistry.d.ts +95 -0
- package/dist/wallet/walletRegistry.d.ts.map +1 -0
- package/dist/wallet/walletRegistry.js +184 -0
- package/dist/wallet/walletRegistry.js.map +1 -0
- package/index.js +2 -0
- package/openclaw.plugin.json +37 -0
- package/package.json +69 -0
- package/src/__mocks__/@sodax/sdk.ts +28 -0
- package/src/__tests__/errors.test.ts +238 -0
- package/src/__tests__/policyEngine.test.ts +354 -0
- package/src/__tests__/positionAggregator.test.ts +271 -0
- package/src/__tests__/setup.ts +35 -0
- package/src/__tests__/sodaxApi.test.ts +203 -0
- package/src/__tests__/walletRegistry.test.ts +155 -0
- package/src/index.ts +376 -0
- package/src/policy/policyEngine.ts +389 -0
- package/src/providers/spokeProviderFactory.ts +283 -0
- package/src/sodax/client.ts +113 -0
- package/src/tools/bridge.ts +425 -0
- package/src/tools/discovery.ts +989 -0
- package/src/tools/moneyMarket.ts +1265 -0
- package/src/tools/portfolio.ts +697 -0
- package/src/tools/swap.ts +926 -0
- package/src/tools/walletManagement.ts +359 -0
- package/src/types.ts +228 -0
- package/src/utils/errorUtils.ts +16 -0
- package/src/utils/errors.ts +396 -0
- package/src/utils/positionAggregator.ts +559 -0
- package/src/utils/priceService.ts +153 -0
- package/src/utils/sodaxApi.ts +261 -0
- package/src/utils/tokenResolver.ts +286 -0
- package/src/wallet/backendConfig.ts +151 -0
- package/src/wallet/backends/BankrBackend.ts +399 -0
- package/src/wallet/backends/BankrWalletProvider.ts +329 -0
- package/src/wallet/backends/EnvBackend.ts +149 -0
- package/src/wallet/backends/EvmWalletSkillBackend.ts +110 -0
- package/src/wallet/backends/index.ts +10 -0
- package/src/wallet/index.ts +14 -0
- package/src/wallet/providers/AmpedWalletProvider.ts +267 -0
- package/src/wallet/providers/BankrBackend.ts +407 -0
- package/src/wallet/providers/LocalKeyBackend.ts +184 -0
- package/src/wallet/providers/chainConfig.ts +194 -0
- package/src/wallet/providers/index.ts +62 -0
- package/src/wallet/providers/types.ts +186 -0
- package/src/wallet/skillWalletAdapter.ts +335 -0
- package/src/wallet/types.ts +248 -0
- package/src/wallet/walletManager.ts +561 -0
- 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 };
|