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,561 @@
1
+ /**
2
+ * Unified Wallet Manager
3
+ *
4
+ * Manages multiple wallet sources with nicknames:
5
+ * - evm-wallet-skill (main)
6
+ * - Bankr (bankr)
7
+ * - Environment variables (custom names)
8
+ *
9
+ * Auto-discovery order:
10
+ * 1. wallets.json config file
11
+ * 2. ~/.evm-wallet.json (evm-wallet-skill) → "main"
12
+ * 3. BANKR_API_KEY env → "bankr"
13
+ * 4. AMPED_OC_WALLETS_JSON env → named wallets
14
+ */
15
+
16
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
17
+ import { join, dirname } from 'path';
18
+ import { homedir } from 'os';
19
+ import type { Address } from 'viem';
20
+ import type { IWalletBackend, WalletInfo, WalletsConfigFile, WalletConfig } from './types';
21
+ import {
22
+ createEvmWalletSkillBackend,
23
+ createBankrBackend,
24
+ createEnvBackend,
25
+ loadWalletsFromEnv
26
+ } from './backends';
27
+
28
+ /**
29
+ * Config file path
30
+ */
31
+ const CONFIG_PATH = join(homedir(), '.openclaw', 'extensions', 'amped-defi', 'wallets.json');
32
+ const EVM_WALLET_PATH = join(homedir(), '.evm-wallet.json');
33
+
34
+ /**
35
+ * Singleton WalletManager instance
36
+ */
37
+ let instance: WalletManager | null = null;
38
+
39
+ /**
40
+ * Unified wallet manager
41
+ */
42
+ export class WalletManager {
43
+ private wallets = new Map<string, IWalletBackend>();
44
+ private defaultWallet: string | null = null;
45
+ private initialized = false;
46
+
47
+ /**
48
+ * Initialize the wallet manager
49
+ * Auto-discovers wallets from all sources
50
+ */
51
+ async initialize(): Promise<void> {
52
+ if (this.initialized) return;
53
+
54
+ console.log('[WalletManager] Initializing...');
55
+
56
+ // 1. Load from config file if exists
57
+ await this.loadConfigFile();
58
+
59
+ // 2. Auto-discover from environment
60
+ await this.autoDiscover();
61
+
62
+ // 3. Set default
63
+ this.determineDefault();
64
+
65
+ this.initialized = true;
66
+
67
+ console.log(`[WalletManager] Initialized with ${this.wallets.size} wallet(s)`);
68
+ if (this.defaultWallet) {
69
+ console.log(`[WalletManager] Default wallet: ${this.defaultWallet}`);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Load wallets from config file
75
+ */
76
+ private async loadConfigFile(): Promise<void> {
77
+ if (!existsSync(CONFIG_PATH)) return;
78
+
79
+ try {
80
+ const content = readFileSync(CONFIG_PATH, 'utf-8');
81
+ const config = JSON.parse(content) as WalletsConfigFile;
82
+
83
+ for (const [name, walletConfig] of Object.entries(config.wallets)) {
84
+ const backend = this.createBackendFromConfig(name, walletConfig);
85
+ if (backend) {
86
+ this.wallets.set(name.toLowerCase(), backend);
87
+ console.log(`[WalletManager] Loaded wallet "${name}" from config`);
88
+ }
89
+ }
90
+
91
+ if (config.default) {
92
+ this.defaultWallet = config.default.toLowerCase();
93
+ }
94
+ } catch (error) {
95
+ console.warn(`[WalletManager] Failed to load config: ${error}`);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Create backend from config entry
101
+ */
102
+ private createBackendFromConfig(name: string, config: WalletConfig): IWalletBackend | null {
103
+ try {
104
+ switch (config.source) {
105
+ case 'evm-wallet-skill':
106
+ return createEvmWalletSkillBackend({
107
+ nickname: name,
108
+ path: config.path,
109
+ chains: config.chains,
110
+ });
111
+
112
+ case 'bankr':
113
+ if (!config.apiKey) {
114
+ console.warn(`[WalletManager] Bankr wallet "${name}" missing apiKey`);
115
+ return null;
116
+ }
117
+ return createBankrBackend({
118
+ nickname: name,
119
+ apiKey: config.apiKey,
120
+ apiUrl: config.apiUrl,
121
+ });
122
+
123
+ case 'env':
124
+ return createEnvBackend({
125
+ nickname: name,
126
+ address: config.address,
127
+ privateKey: config.privateKey,
128
+ envVar: config.envVar,
129
+ chains: config.chains,
130
+ });
131
+
132
+ default:
133
+ console.warn(`[WalletManager] Unknown wallet source: ${config.source}`);
134
+ return null;
135
+ }
136
+ } catch (error) {
137
+ console.warn(`[WalletManager] Failed to create backend for "${name}": ${error}`);
138
+ return null;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Auto-discover wallets from environment
144
+ */
145
+ private async autoDiscover(): Promise<void> {
146
+ // evm-wallet-skill (if not already configured)
147
+ if (!this.wallets.has('main') && existsSync(EVM_WALLET_PATH)) {
148
+ try {
149
+ const backend = createEvmWalletSkillBackend({ nickname: 'main' });
150
+ if (await backend.isReady()) {
151
+ this.wallets.set('main', backend);
152
+ console.log('[WalletManager] Auto-discovered: evm-wallet-skill → "main"');
153
+ }
154
+ } catch (error) {
155
+ console.debug(`[WalletManager] evm-wallet-skill not available: ${error}`);
156
+ }
157
+ }
158
+
159
+ // Bankr (if API key present and not already configured)
160
+ const bankrApiKey = process.env.BANKR_API_KEY;
161
+ if (!this.wallets.has('bankr') && bankrApiKey) {
162
+ console.log('[WalletManager] Found BANKR_API_KEY, attempting to add Bankr wallet...');
163
+ try {
164
+ const backend = createBankrBackend({
165
+ nickname: 'bankr',
166
+ apiKey: bankrApiKey,
167
+ apiUrl: process.env.BANKR_API_URL,
168
+ });
169
+ const ready = await backend.isReady();
170
+ if (ready) {
171
+ this.wallets.set('bankr', backend);
172
+ console.log('[WalletManager] Auto-discovered: BANKR_API_KEY → "bankr"');
173
+ } else {
174
+ console.warn('[WalletManager] Bankr API key present but connectivity check failed');
175
+ }
176
+ } catch (error) {
177
+ console.warn(`[WalletManager] Bankr auto-discovery failed: ${error}`);
178
+ }
179
+ }
180
+
181
+ // Environment variable wallets
182
+ const envWallets = loadWalletsFromEnv();
183
+ for (const [name, backend] of envWallets) {
184
+ if (!this.wallets.has(name)) {
185
+ this.wallets.set(name, backend);
186
+ console.log(`[WalletManager] Auto-discovered: AMPED_OC_WALLETS_JSON → "${name}"`);
187
+ }
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Determine default wallet
193
+ */
194
+ private determineDefault(): void {
195
+ // If already set from config, verify it exists
196
+ if (this.defaultWallet && this.wallets.has(this.defaultWallet)) {
197
+ return;
198
+ }
199
+
200
+ // Priority: main > first available
201
+ if (this.wallets.has('main')) {
202
+ this.defaultWallet = 'main';
203
+ } else if (this.wallets.size > 0) {
204
+ this.defaultWallet = Array.from(this.wallets.keys())[0];
205
+ } else {
206
+ this.defaultWallet = null;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Resolve a wallet by nickname
212
+ * @param nickname Optional wallet nickname (uses default if not provided)
213
+ */
214
+ async resolve(nickname?: string): Promise<IWalletBackend> {
215
+ await this.initialize();
216
+
217
+ const name = (nickname || this.defaultWallet)?.toLowerCase();
218
+
219
+ if (!name) {
220
+ throw new Error(
221
+ 'No wallet configured.\n\n' +
222
+ 'To set up a wallet, install evm-wallet-skill:\n' +
223
+ ' git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\n' +
224
+ ' cd ~/.openclaw/skills/evm-wallet-skill && npm install\n' +
225
+ ' node src/setup.js'
226
+ );
227
+ }
228
+
229
+ const wallet = this.wallets.get(name);
230
+ if (!wallet) {
231
+ const available = Array.from(this.wallets.keys()).join(', ') || '(none)';
232
+ throw new Error(`Wallet "${name}" not found. Available wallets: ${available}`);
233
+ }
234
+
235
+ return wallet;
236
+ }
237
+
238
+ /**
239
+ * Check if a wallet exists
240
+ */
241
+ async has(nickname: string): Promise<boolean> {
242
+ await this.initialize();
243
+ return this.wallets.has(nickname.toLowerCase());
244
+ }
245
+
246
+ /**
247
+ * List all available wallets
248
+ */
249
+ async listWallets(): Promise<WalletInfo[]> {
250
+ await this.initialize();
251
+
252
+ const wallets: WalletInfo[] = [];
253
+
254
+ for (const [name, backend] of this.wallets) {
255
+ try {
256
+ // Add timeout for slow backends (like Bankr)
257
+ const addressPromise = backend.getAddress();
258
+ const timeoutPromise = new Promise<never>((_, reject) =>
259
+ setTimeout(() => reject(new Error('Timeout')), 30000)
260
+ );
261
+
262
+ const address = await Promise.race([addressPromise, timeoutPromise]);
263
+
264
+ // Get Solana address for Bankr wallets
265
+ let solanaAddress: string | undefined;
266
+ if (backend.type === 'bankr' && (backend as any).getSolanaAddress) {
267
+ try {
268
+ solanaAddress = await (backend as any).getSolanaAddress() || undefined;
269
+ } catch (e) {
270
+ console.warn(`[WalletManager] Failed to get Solana address for ${name}`);
271
+ }
272
+ }
273
+
274
+ wallets.push({
275
+ nickname: name,
276
+ type: backend.type,
277
+ address,
278
+ chains: [...backend.supportedChains],
279
+ isDefault: name === this.defaultWallet,
280
+ solanaAddress,
281
+ });
282
+ } catch (error) {
283
+ // Include wallet with placeholder address if we can't get it
284
+ console.warn(`[WalletManager] Failed to get address for "${name}": ${error}`);
285
+ wallets.push({
286
+ nickname: name,
287
+ type: backend.type,
288
+ address: '0x...' as Address, // Placeholder
289
+ chains: [...backend.supportedChains],
290
+ isDefault: name === this.defaultWallet,
291
+ });
292
+ }
293
+ }
294
+
295
+ return wallets;
296
+ }
297
+
298
+ /**
299
+ * Get the default wallet nickname
300
+ */
301
+ async getDefaultWalletName(): Promise<string | null> {
302
+ await this.initialize();
303
+ return this.defaultWallet;
304
+ }
305
+
306
+ /**
307
+ * Register a new wallet backend
308
+ */
309
+ registerWallet(nickname: string, backend: IWalletBackend): void {
310
+ this.wallets.set(nickname.toLowerCase(), backend);
311
+ console.log(`[WalletManager] Registered wallet: ${nickname}`);
312
+ }
313
+
314
+ /**
315
+ * Get available wallet IDs (nicknames)
316
+ * Synchronous version - requires prior initialization
317
+ */
318
+ getAvailableWalletIds(): string[] {
319
+ return Array.from(this.wallets.keys());
320
+ }
321
+
322
+ /**
323
+ * Add a new wallet to the config file
324
+ */
325
+ async addWallet(nickname: string, config: WalletConfig): Promise<void> {
326
+ await this.initialize();
327
+
328
+ const normalizedName = nickname.toLowerCase();
329
+
330
+ // Check if wallet already exists
331
+ if (this.wallets.has(normalizedName)) {
332
+ throw new Error(`Wallet "${nickname}" already exists. Use rename to change it.`);
333
+ }
334
+
335
+ // Create the backend to validate config
336
+ const backend = this.createBackendFromConfig(normalizedName, config);
337
+ if (!backend) {
338
+ throw new Error(`Failed to create wallet backend for "${nickname}"`);
339
+ }
340
+
341
+ // Validate the backend works
342
+ const ready = await backend.isReady();
343
+ if (!ready) {
344
+ throw new Error(`Wallet "${nickname}" configuration is invalid or not accessible`);
345
+ }
346
+
347
+ // Load existing config
348
+ const fileConfig = this.loadConfigFromFile();
349
+
350
+ // Add new wallet
351
+ fileConfig.wallets[normalizedName] = config;
352
+
353
+ // Save config
354
+ this.saveConfigToFile(fileConfig);
355
+
356
+ // Register in memory
357
+ this.wallets.set(normalizedName, backend);
358
+
359
+ console.log(`[WalletManager] Added wallet "${nickname}"`);
360
+ }
361
+
362
+ /**
363
+ * Rename a wallet
364
+ */
365
+ async renameWallet(currentNickname: string, newNickname: string): Promise<void> {
366
+ await this.initialize();
367
+
368
+ const currentName = currentNickname.toLowerCase();
369
+ const newName = newNickname.toLowerCase();
370
+
371
+ // Check source exists
372
+ if (!this.wallets.has(currentName)) {
373
+ throw new Error(`Wallet "${currentNickname}" not found`);
374
+ }
375
+
376
+ // Check target doesn't exist
377
+ if (this.wallets.has(newName)) {
378
+ throw new Error(`Wallet "${newNickname}" already exists`);
379
+ }
380
+
381
+ // Load config
382
+ const fileConfig = this.loadConfigFromFile();
383
+
384
+ // Move wallet config
385
+ if (fileConfig.wallets[currentName]) {
386
+ fileConfig.wallets[newName] = fileConfig.wallets[currentName];
387
+ delete fileConfig.wallets[currentName];
388
+ } else {
389
+ // Wallet was auto-discovered, need to add it to config
390
+ const backend = this.wallets.get(currentName)!;
391
+ const config = await this.backendToConfig(backend);
392
+ fileConfig.wallets[newName] = config;
393
+ }
394
+
395
+ // Update default if needed
396
+ if (fileConfig.default === currentName) {
397
+ fileConfig.default = newName;
398
+ }
399
+ if (this.defaultWallet === currentName) {
400
+ this.defaultWallet = newName;
401
+ }
402
+
403
+ // Save config
404
+ this.saveConfigToFile(fileConfig);
405
+
406
+ // Update in-memory
407
+ const backend = this.wallets.get(currentName)!;
408
+ this.wallets.delete(currentName);
409
+ this.wallets.set(newName, backend);
410
+
411
+ console.log(`[WalletManager] Renamed wallet "${currentNickname}" to "${newNickname}"`);
412
+ }
413
+
414
+ /**
415
+ * Remove a wallet from config
416
+ */
417
+ async removeWallet(nickname: string): Promise<void> {
418
+ await this.initialize();
419
+
420
+ const name = nickname.toLowerCase();
421
+
422
+ if (!this.wallets.has(name)) {
423
+ throw new Error(`Wallet "${nickname}" not found`);
424
+ }
425
+
426
+ // Load config
427
+ const fileConfig = this.loadConfigFromFile();
428
+
429
+ // Remove from config
430
+ delete fileConfig.wallets[name];
431
+
432
+ // Update default if needed
433
+ if (fileConfig.default === name) {
434
+ delete fileConfig.default;
435
+ }
436
+ if (this.defaultWallet === name) {
437
+ this.defaultWallet = null;
438
+ this.determineDefault();
439
+ }
440
+
441
+ // Save config
442
+ this.saveConfigToFile(fileConfig);
443
+
444
+ // Remove from memory
445
+ this.wallets.delete(name);
446
+
447
+ console.log(`[WalletManager] Removed wallet "${nickname}"`);
448
+ }
449
+
450
+ /**
451
+ * Set the default wallet
452
+ */
453
+ async setDefaultWallet(nickname: string): Promise<void> {
454
+ await this.initialize();
455
+
456
+ const name = nickname.toLowerCase();
457
+
458
+ if (!this.wallets.has(name)) {
459
+ throw new Error(`Wallet "${nickname}" not found`);
460
+ }
461
+
462
+ // Load config
463
+ const fileConfig = this.loadConfigFromFile();
464
+
465
+ // Update default
466
+ fileConfig.default = name;
467
+ this.defaultWallet = name;
468
+
469
+ // Save config
470
+ this.saveConfigToFile(fileConfig);
471
+
472
+ console.log(`[WalletManager] Set default wallet to "${nickname}"`);
473
+ }
474
+
475
+ /**
476
+ * Load config from file (creates empty if doesn't exist)
477
+ */
478
+ private loadConfigFromFile(): WalletsConfigFile {
479
+ if (!existsSync(CONFIG_PATH)) {
480
+ return { wallets: {} };
481
+ }
482
+
483
+ try {
484
+ const content = readFileSync(CONFIG_PATH, 'utf-8');
485
+ return JSON.parse(content) as WalletsConfigFile;
486
+ } catch {
487
+ return { wallets: {} };
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Save config to file
493
+ */
494
+ private saveConfigToFile(config: WalletsConfigFile): void {
495
+ // Ensure directory exists
496
+ const dir = dirname(CONFIG_PATH);
497
+ if (!existsSync(dir)) {
498
+ mkdirSync(dir, { recursive: true });
499
+ }
500
+
501
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
502
+ console.log(`[WalletManager] Config saved to ${CONFIG_PATH}`);
503
+ }
504
+
505
+ /**
506
+ * Convert a backend to config (for saving auto-discovered wallets)
507
+ */
508
+ private async backendToConfig(backend: IWalletBackend): Promise<WalletConfig> {
509
+ const config: WalletConfig = {
510
+ source: backend.type,
511
+ chains: [...backend.supportedChains],
512
+ };
513
+
514
+ // For evm-wallet-skill, just reference the default path
515
+ if (backend.type === 'evm-wallet-skill') {
516
+ config.path = EVM_WALLET_PATH;
517
+ }
518
+
519
+ // For env backends, we need address (privateKey should NOT be saved)
520
+ if (backend.type === 'env') {
521
+ config.address = await backend.getAddress();
522
+ // Note: We don't save privateKey to config for security
523
+ }
524
+
525
+ // For bankr, we need the API key
526
+ if (backend.type === 'bankr') {
527
+ config.apiKey = process.env.BANKR_API_KEY;
528
+ }
529
+
530
+ return config;
531
+ }
532
+
533
+ /**
534
+ * Reset the manager (for testing)
535
+ */
536
+ reset(): void {
537
+ this.wallets.clear();
538
+ this.defaultWallet = null;
539
+ this.initialized = false;
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Get the singleton WalletManager instance
545
+ */
546
+ export function getWalletManager(): WalletManager {
547
+ if (!instance) {
548
+ instance = new WalletManager();
549
+ }
550
+ return instance;
551
+ }
552
+
553
+ /**
554
+ * Reset the singleton (for testing)
555
+ */
556
+ export function resetWalletManager(): void {
557
+ if (instance) {
558
+ instance.reset();
559
+ instance = null;
560
+ }
561
+ }