@quantulabs/8004-mcp 0.1.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 (243) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +370 -0
  3. package/dist/chains/evm/index.d.ts +2 -0
  4. package/dist/chains/evm/index.d.ts.map +1 -0
  5. package/dist/chains/evm/index.js +3 -0
  6. package/dist/chains/evm/index.js.map +1 -0
  7. package/dist/chains/evm/provider.d.ts +48 -0
  8. package/dist/chains/evm/provider.d.ts.map +1 -0
  9. package/dist/chains/evm/provider.js +446 -0
  10. package/dist/chains/evm/provider.js.map +1 -0
  11. package/dist/chains/index.d.ts +3 -0
  12. package/dist/chains/index.d.ts.map +1 -0
  13. package/dist/chains/index.js +4 -0
  14. package/dist/chains/index.js.map +1 -0
  15. package/dist/chains/solana/data-source.d.ts +17 -0
  16. package/dist/chains/solana/data-source.d.ts.map +1 -0
  17. package/dist/chains/solana/data-source.js +65 -0
  18. package/dist/chains/solana/data-source.js.map +1 -0
  19. package/dist/chains/solana/index.d.ts +8 -0
  20. package/dist/chains/solana/index.d.ts.map +1 -0
  21. package/dist/chains/solana/index.js +9 -0
  22. package/dist/chains/solana/index.js.map +1 -0
  23. package/dist/chains/solana/parsers.d.ts +11 -0
  24. package/dist/chains/solana/parsers.d.ts.map +1 -0
  25. package/dist/chains/solana/parsers.js +94 -0
  26. package/dist/chains/solana/parsers.js.map +1 -0
  27. package/dist/chains/solana/provider.d.ts +33 -0
  28. package/dist/chains/solana/provider.d.ts.map +1 -0
  29. package/dist/chains/solana/provider.js +407 -0
  30. package/dist/chains/solana/provider.js.map +1 -0
  31. package/dist/chains/solana/state.d.ts +55 -0
  32. package/dist/chains/solana/state.d.ts.map +1 -0
  33. package/dist/chains/solana/state.js +162 -0
  34. package/dist/chains/solana/state.js.map +1 -0
  35. package/dist/chains/solana/tools/atom.d.ts +45 -0
  36. package/dist/chains/solana/tools/atom.d.ts.map +1 -0
  37. package/dist/chains/solana/tools/atom.js +115 -0
  38. package/dist/chains/solana/tools/atom.js.map +1 -0
  39. package/dist/chains/solana/tools/validation.d.ts +45 -0
  40. package/dist/chains/solana/tools/validation.d.ts.map +1 -0
  41. package/dist/chains/solana/tools/validation.js +212 -0
  42. package/dist/chains/solana/tools/validation.js.map +1 -0
  43. package/dist/chains/solana/tools/wallet.d.ts +45 -0
  44. package/dist/chains/solana/tools/wallet.d.ts.map +1 -0
  45. package/dist/chains/solana/tools/wallet.js +103 -0
  46. package/dist/chains/solana/tools/wallet.js.map +1 -0
  47. package/dist/config/defaults.d.ts +68 -0
  48. package/dist/config/defaults.d.ts.map +1 -0
  49. package/dist/config/defaults.js +247 -0
  50. package/dist/config/defaults.js.map +1 -0
  51. package/dist/config/env.d.ts +33 -0
  52. package/dist/config/env.d.ts.map +1 -0
  53. package/dist/config/env.js +42 -0
  54. package/dist/config/env.js.map +1 -0
  55. package/dist/config/index.d.ts +3 -0
  56. package/dist/config/index.d.ts.map +1 -0
  57. package/dist/config/index.js +3 -0
  58. package/dist/config/index.js.map +1 -0
  59. package/dist/core/cache/agent-cache.d.ts +36 -0
  60. package/dist/core/cache/agent-cache.d.ts.map +1 -0
  61. package/dist/core/cache/agent-cache.js +87 -0
  62. package/dist/core/cache/agent-cache.js.map +1 -0
  63. package/dist/core/cache/data-source.d.ts +31 -0
  64. package/dist/core/cache/data-source.d.ts.map +1 -0
  65. package/dist/core/cache/data-source.js +6 -0
  66. package/dist/core/cache/data-source.js.map +1 -0
  67. package/dist/core/cache/index.d.ts +7 -0
  68. package/dist/core/cache/index.d.ts.map +1 -0
  69. package/dist/core/cache/index.js +9 -0
  70. package/dist/core/cache/index.js.map +1 -0
  71. package/dist/core/cache/lazy-cache.d.ts +30 -0
  72. package/dist/core/cache/lazy-cache.d.ts.map +1 -0
  73. package/dist/core/cache/lazy-cache.js +79 -0
  74. package/dist/core/cache/lazy-cache.js.map +1 -0
  75. package/dist/core/cache/slim-store.d.ts +66 -0
  76. package/dist/core/cache/slim-store.d.ts.map +1 -0
  77. package/dist/core/cache/slim-store.js +285 -0
  78. package/dist/core/cache/slim-store.js.map +1 -0
  79. package/dist/core/cache/sqlite-store.d.ts +99 -0
  80. package/dist/core/cache/sqlite-store.d.ts.map +1 -0
  81. package/dist/core/cache/sqlite-store.js +470 -0
  82. package/dist/core/cache/sqlite-store.js.map +1 -0
  83. package/dist/core/cache/sync-manager.d.ts +38 -0
  84. package/dist/core/cache/sync-manager.d.ts.map +1 -0
  85. package/dist/core/cache/sync-manager.js +213 -0
  86. package/dist/core/cache/sync-manager.js.map +1 -0
  87. package/dist/core/errors/index.d.ts +2 -0
  88. package/dist/core/errors/index.d.ts.map +1 -0
  89. package/dist/core/errors/index.js +2 -0
  90. package/dist/core/errors/index.js.map +1 -0
  91. package/dist/core/errors/mcp-error.d.ts +39 -0
  92. package/dist/core/errors/mcp-error.d.ts.map +1 -0
  93. package/dist/core/errors/mcp-error.js +79 -0
  94. package/dist/core/errors/mcp-error.js.map +1 -0
  95. package/dist/core/interfaces/agent.d.ts +84 -0
  96. package/dist/core/interfaces/agent.d.ts.map +1 -0
  97. package/dist/core/interfaces/agent.js +40 -0
  98. package/dist/core/interfaces/agent.js.map +1 -0
  99. package/dist/core/interfaces/chain-provider.d.ts +50 -0
  100. package/dist/core/interfaces/chain-provider.d.ts.map +1 -0
  101. package/dist/core/interfaces/chain-provider.js +5 -0
  102. package/dist/core/interfaces/chain-provider.js.map +1 -0
  103. package/dist/core/interfaces/feedback.d.ts +46 -0
  104. package/dist/core/interfaces/feedback.d.ts.map +1 -0
  105. package/dist/core/interfaces/feedback.js +3 -0
  106. package/dist/core/interfaces/feedback.js.map +1 -0
  107. package/dist/core/interfaces/index.d.ts +6 -0
  108. package/dist/core/interfaces/index.d.ts.map +1 -0
  109. package/dist/core/interfaces/index.js +7 -0
  110. package/dist/core/interfaces/index.js.map +1 -0
  111. package/dist/core/interfaces/reputation.d.ts +47 -0
  112. package/dist/core/interfaces/reputation.d.ts.map +1 -0
  113. package/dist/core/interfaces/reputation.js +30 -0
  114. package/dist/core/interfaces/reputation.js.map +1 -0
  115. package/dist/core/interfaces/x402.d.ts +226 -0
  116. package/dist/core/interfaces/x402.d.ts.map +1 -0
  117. package/dist/core/interfaces/x402.js +120 -0
  118. package/dist/core/interfaces/x402.js.map +1 -0
  119. package/dist/core/parsers/common.d.ts +31 -0
  120. package/dist/core/parsers/common.d.ts.map +1 -0
  121. package/dist/core/parsers/common.js +185 -0
  122. package/dist/core/parsers/common.js.map +1 -0
  123. package/dist/core/parsers/index.d.ts +2 -0
  124. package/dist/core/parsers/index.d.ts.map +1 -0
  125. package/dist/core/parsers/index.js +2 -0
  126. package/dist/core/parsers/index.js.map +1 -0
  127. package/dist/core/registry/chain-registry.d.ts +26 -0
  128. package/dist/core/registry/chain-registry.d.ts.map +1 -0
  129. package/dist/core/registry/chain-registry.js +141 -0
  130. package/dist/core/registry/chain-registry.js.map +1 -0
  131. package/dist/core/registry/index.d.ts +3 -0
  132. package/dist/core/registry/index.d.ts.map +1 -0
  133. package/dist/core/registry/index.js +3 -0
  134. package/dist/core/registry/index.js.map +1 -0
  135. package/dist/core/registry/tool-registry.d.ts +23 -0
  136. package/dist/core/registry/tool-registry.d.ts.map +1 -0
  137. package/dist/core/registry/tool-registry.js +66 -0
  138. package/dist/core/registry/tool-registry.js.map +1 -0
  139. package/dist/core/serializers/common.d.ts +20 -0
  140. package/dist/core/serializers/common.d.ts.map +1 -0
  141. package/dist/core/serializers/common.js +76 -0
  142. package/dist/core/serializers/common.js.map +1 -0
  143. package/dist/core/serializers/index.d.ts +2 -0
  144. package/dist/core/serializers/index.d.ts.map +1 -0
  145. package/dist/core/serializers/index.js +2 -0
  146. package/dist/core/serializers/index.js.map +1 -0
  147. package/dist/core/services/index.d.ts +3 -0
  148. package/dist/core/services/index.d.ts.map +1 -0
  149. package/dist/core/services/index.js +3 -0
  150. package/dist/core/services/index.js.map +1 -0
  151. package/dist/core/services/ipfs-service.d.ts +59 -0
  152. package/dist/core/services/ipfs-service.d.ts.map +1 -0
  153. package/dist/core/services/ipfs-service.js +84 -0
  154. package/dist/core/services/ipfs-service.js.map +1 -0
  155. package/dist/core/utils/tags.d.ts +38 -0
  156. package/dist/core/utils/tags.d.ts.map +1 -0
  157. package/dist/core/utils/tags.js +77 -0
  158. package/dist/core/utils/tags.js.map +1 -0
  159. package/dist/core/utils/value-encoding.d.ts +36 -0
  160. package/dist/core/utils/value-encoding.d.ts.map +1 -0
  161. package/dist/core/utils/value-encoding.js +196 -0
  162. package/dist/core/utils/value-encoding.js.map +1 -0
  163. package/dist/core/wallet/index.d.ts +2 -0
  164. package/dist/core/wallet/index.d.ts.map +1 -0
  165. package/dist/core/wallet/index.js +3 -0
  166. package/dist/core/wallet/index.js.map +1 -0
  167. package/dist/core/wallet/wallet-manager.d.ts +86 -0
  168. package/dist/core/wallet/wallet-manager.d.ts.map +1 -0
  169. package/dist/core/wallet/wallet-manager.js +783 -0
  170. package/dist/core/wallet/wallet-manager.js.map +1 -0
  171. package/dist/core/x402/index.d.ts +2 -0
  172. package/dist/core/x402/index.d.ts.map +1 -0
  173. package/dist/core/x402/index.js +3 -0
  174. package/dist/core/x402/index.js.map +1 -0
  175. package/dist/core/x402/proof-validator.d.ts +57 -0
  176. package/dist/core/x402/proof-validator.d.ts.map +1 -0
  177. package/dist/core/x402/proof-validator.js +103 -0
  178. package/dist/core/x402/proof-validator.js.map +1 -0
  179. package/dist/index.d.ts +3 -0
  180. package/dist/index.d.ts.map +1 -0
  181. package/dist/index.js +135 -0
  182. package/dist/index.js.map +1 -0
  183. package/dist/state/global-state.d.ts +98 -0
  184. package/dist/state/global-state.d.ts.map +1 -0
  185. package/dist/state/global-state.js +258 -0
  186. package/dist/state/global-state.js.map +1 -0
  187. package/dist/tools/definitions.d.ts +10 -0
  188. package/dist/tools/definitions.d.ts.map +1 -0
  189. package/dist/tools/definitions.js +163 -0
  190. package/dist/tools/definitions.js.map +1 -0
  191. package/dist/tools/unified/agent.d.ts +5 -0
  192. package/dist/tools/unified/agent.d.ts.map +1 -0
  193. package/dist/tools/unified/agent.js +300 -0
  194. package/dist/tools/unified/agent.js.map +1 -0
  195. package/dist/tools/unified/cache.d.ts +5 -0
  196. package/dist/tools/unified/cache.d.ts.map +1 -0
  197. package/dist/tools/unified/cache.js +207 -0
  198. package/dist/tools/unified/cache.js.map +1 -0
  199. package/dist/tools/unified/collection.d.ts +5 -0
  200. package/dist/tools/unified/collection.d.ts.map +1 -0
  201. package/dist/tools/unified/collection.js +162 -0
  202. package/dist/tools/unified/collection.js.map +1 -0
  203. package/dist/tools/unified/config.d.ts +5 -0
  204. package/dist/tools/unified/config.d.ts.map +1 -0
  205. package/dist/tools/unified/config.js +217 -0
  206. package/dist/tools/unified/config.js.map +1 -0
  207. package/dist/tools/unified/crawler.d.ts +5 -0
  208. package/dist/tools/unified/crawler.d.ts.map +1 -0
  209. package/dist/tools/unified/crawler.js +212 -0
  210. package/dist/tools/unified/crawler.js.map +1 -0
  211. package/dist/tools/unified/feedback.d.ts +5 -0
  212. package/dist/tools/unified/feedback.d.ts.map +1 -0
  213. package/dist/tools/unified/feedback.js +274 -0
  214. package/dist/tools/unified/feedback.js.map +1 -0
  215. package/dist/tools/unified/ipfs.d.ts +5 -0
  216. package/dist/tools/unified/ipfs.d.ts.map +1 -0
  217. package/dist/tools/unified/ipfs.js +156 -0
  218. package/dist/tools/unified/ipfs.js.map +1 -0
  219. package/dist/tools/unified/oasf.d.ts +5 -0
  220. package/dist/tools/unified/oasf.d.ts.map +1 -0
  221. package/dist/tools/unified/oasf.js +167 -0
  222. package/dist/tools/unified/oasf.js.map +1 -0
  223. package/dist/tools/unified/registration.d.ts +5 -0
  224. package/dist/tools/unified/registration.d.ts.map +1 -0
  225. package/dist/tools/unified/registration.js +223 -0
  226. package/dist/tools/unified/registration.js.map +1 -0
  227. package/dist/tools/unified/reputation.d.ts +5 -0
  228. package/dist/tools/unified/reputation.d.ts.map +1 -0
  229. package/dist/tools/unified/reputation.js +121 -0
  230. package/dist/tools/unified/reputation.js.map +1 -0
  231. package/dist/tools/unified/wallet.d.ts +5 -0
  232. package/dist/tools/unified/wallet.d.ts.map +1 -0
  233. package/dist/tools/unified/wallet.js +434 -0
  234. package/dist/tools/unified/wallet.js.map +1 -0
  235. package/dist/tools/unified/write-operations.d.ts +5 -0
  236. package/dist/tools/unified/write-operations.d.ts.map +1 -0
  237. package/dist/tools/unified/write-operations.js +582 -0
  238. package/dist/tools/unified/write-operations.js.map +1 -0
  239. package/dist/tools/unified/x402.d.ts +5 -0
  240. package/dist/tools/unified/x402.d.ts.map +1 -0
  241. package/dist/tools/unified/x402.js +594 -0
  242. package/dist/tools/unified/x402.js.map +1 -0
  243. package/package.json +67 -0
@@ -0,0 +1,783 @@
1
+ // Multi-wallet manager with AES-256-GCM encryption and Argon2id key derivation
2
+ // Supports Solana (Ed25519) and EVM (secp256k1) wallets
3
+ import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';
4
+ import { Keypair } from '@solana/web3.js';
5
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
6
+ import * as argon2 from 'argon2';
7
+ import { promises as fs } from 'fs';
8
+ import { join } from 'path';
9
+ import { homedir } from 'os';
10
+ // Crypto constants
11
+ const ALGORITHM = 'aes-256-gcm';
12
+ const KEY_LENGTH = 32; // 256 bits
13
+ const NONCE_LENGTH = 12; // 96 bits for GCM
14
+ const SALT_LENGTH = 32; // 256 bits
15
+ // Argon2id parameters (OWASP recommended for password hashing)
16
+ const ARGON2_OPTIONS = {
17
+ type: argon2.argon2id,
18
+ memoryCost: 65536, // 64 MB
19
+ timeCost: 3, // 3 iterations
20
+ parallelism: 4, // 4 parallel threads
21
+ hashLength: KEY_LENGTH,
22
+ };
23
+ // Auto-lock configuration
24
+ const DEFAULT_AUTO_LOCK_MS = 15 * 60 * 1000; // 15 minutes default
25
+ const MIN_AUTO_LOCK_MS = 60 * 1000; // 1 minute minimum
26
+ const MAX_AUTO_LOCK_MS = 24 * 60 * 60 * 1000; // 24 hours maximum
27
+ export class WalletManager {
28
+ walletDir;
29
+ walletsPath;
30
+ unlockedWallets = new Map();
31
+ walletSessions = new Map();
32
+ autoLockMs = DEFAULT_AUTO_LOCK_MS;
33
+ constructor(walletDir) {
34
+ this.walletDir = walletDir ?? join(homedir(), '.8004-mcp');
35
+ this.walletsPath = join(this.walletDir, 'wallets');
36
+ }
37
+ // Configure auto-lock timeout (in milliseconds)
38
+ setAutoLockTimeout(ms) {
39
+ this.autoLockMs = Math.max(MIN_AUTO_LOCK_MS, Math.min(MAX_AUTO_LOCK_MS, ms));
40
+ }
41
+ // Get current auto-lock timeout
42
+ getAutoLockTimeout() {
43
+ return this.autoLockMs;
44
+ }
45
+ // Start or refresh auto-lock timer for a wallet
46
+ refreshAutoLock(name) {
47
+ const session = this.walletSessions.get(name);
48
+ if (session) {
49
+ // Clear existing timer
50
+ clearTimeout(session.timer);
51
+ // Set new timer
52
+ session.timer = setTimeout(() => this.autoLockWallet(name), this.autoLockMs);
53
+ session.lastActivity = Date.now();
54
+ }
55
+ }
56
+ // Auto-lock a wallet (called by timer)
57
+ autoLockWallet(name) {
58
+ const session = this.walletSessions.get(name);
59
+ if (session) {
60
+ // Securely clear the wallet from memory
61
+ this.secureWipe(session.wallet);
62
+ this.walletSessions.delete(name);
63
+ this.unlockedWallets.delete(name);
64
+ }
65
+ }
66
+ // Securely wipe sensitive data from memory
67
+ secureWipe(wallet) {
68
+ // Overwrite Solana keypair secret key with zeros
69
+ if (wallet.solanaKeypair) {
70
+ const secretKey = wallet.solanaKeypair.secretKey;
71
+ for (let i = 0; i < secretKey.length; i++) {
72
+ secretKey[i] = 0;
73
+ }
74
+ }
75
+ // Overwrite EVM private key string (limited in JS, but try)
76
+ if (wallet.evmPrivateKey) {
77
+ // TypeScript won't let us mutate, but we can delete the reference
78
+ wallet.evmPrivateKey = undefined;
79
+ }
80
+ if (wallet.evmAccount) {
81
+ wallet.evmAccount = undefined;
82
+ }
83
+ }
84
+ // Touch a wallet to refresh its auto-lock timer (call on every operation)
85
+ touchWallet(name) {
86
+ if (this.walletSessions.has(name)) {
87
+ this.refreshAutoLock(name);
88
+ return true;
89
+ }
90
+ return false;
91
+ }
92
+ // Derive encryption key from password using Argon2id
93
+ async deriveKey(password, salt) {
94
+ const hash = await argon2.hash(password, {
95
+ ...ARGON2_OPTIONS,
96
+ salt,
97
+ raw: true,
98
+ });
99
+ return Buffer.from(hash);
100
+ }
101
+ // Encrypt private key with AES-256-GCM
102
+ encrypt(secretKey, key, nonce) {
103
+ const cipher = createCipheriv(ALGORITHM, key, nonce);
104
+ const ciphertext = Buffer.concat([
105
+ cipher.update(Buffer.from(secretKey)),
106
+ cipher.final(),
107
+ ]);
108
+ const authTag = cipher.getAuthTag();
109
+ return { ciphertext, authTag };
110
+ }
111
+ // Decrypt private key with AES-256-GCM
112
+ decrypt(ciphertext, key, nonce, authTag) {
113
+ const decipher = createDecipheriv(ALGORITHM, key, nonce);
114
+ decipher.setAuthTag(authTag);
115
+ return Buffer.concat([
116
+ decipher.update(ciphertext),
117
+ decipher.final(),
118
+ ]);
119
+ }
120
+ // Ensure wallets directory exists
121
+ async ensureDir() {
122
+ try {
123
+ await fs.mkdir(this.walletsPath, { recursive: true, mode: 0o700 });
124
+ }
125
+ catch (err) {
126
+ if (err.code !== 'EEXIST')
127
+ throw err;
128
+ }
129
+ }
130
+ // Get wallet file path from name
131
+ getWalletPath(name) {
132
+ // Sanitize name for filesystem
133
+ const safeName = name.toLowerCase().replace(/[^a-z0-9-_]/g, '-');
134
+ return join(this.walletsPath, `${safeName}.enc`);
135
+ }
136
+ // Check if wallet exists by name
137
+ async exists(name) {
138
+ try {
139
+ await fs.access(this.getWalletPath(name));
140
+ return true;
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ // Read wallet file (without decrypting)
147
+ async readWalletFile(name) {
148
+ try {
149
+ const content = await fs.readFile(this.getWalletPath(name), 'utf-8');
150
+ return JSON.parse(content);
151
+ }
152
+ catch {
153
+ return null;
154
+ }
155
+ }
156
+ // List all wallets
157
+ async list() {
158
+ await this.ensureDir();
159
+ try {
160
+ const files = await fs.readdir(this.walletsPath);
161
+ const wallets = [];
162
+ for (const file of files) {
163
+ if (!file.endsWith('.enc'))
164
+ continue;
165
+ const filePath = join(this.walletsPath, file);
166
+ try {
167
+ const content = await fs.readFile(filePath, 'utf-8');
168
+ const wallet = JSON.parse(content);
169
+ wallets.push({
170
+ name: wallet.name,
171
+ chainType: wallet.chainType,
172
+ address: wallet.address,
173
+ publicKey: wallet.publicKey,
174
+ createdAt: wallet.createdAt,
175
+ isUnlocked: this.unlockedWallets.has(wallet.name),
176
+ });
177
+ }
178
+ catch {
179
+ // Skip invalid files
180
+ }
181
+ }
182
+ // Sort by name
183
+ wallets.sort((a, b) => a.name.localeCompare(b.name));
184
+ return {
185
+ wallets,
186
+ unlockedCount: this.unlockedWallets.size,
187
+ };
188
+ }
189
+ catch {
190
+ return { wallets: [], unlockedCount: 0 };
191
+ }
192
+ }
193
+ // Get wallet info by name
194
+ async getInfo(name) {
195
+ const walletFile = await this.readWalletFile(name);
196
+ if (!walletFile)
197
+ return null;
198
+ return {
199
+ name: walletFile.name,
200
+ chainType: walletFile.chainType,
201
+ address: walletFile.address,
202
+ publicKey: walletFile.publicKey,
203
+ createdAt: walletFile.createdAt,
204
+ isUnlocked: this.unlockedWallets.has(walletFile.name),
205
+ };
206
+ }
207
+ // Create new wallet
208
+ async create(name, chainType, password) {
209
+ // Check if wallet already exists
210
+ if (await this.exists(name)) {
211
+ throw new Error(`Wallet "${name}" already exists. Use a different name or delete the existing wallet.`);
212
+ }
213
+ // Validate password strength
214
+ if (password.length < 8) {
215
+ throw new Error('Password must be at least 8 characters long.');
216
+ }
217
+ // Validate name
218
+ if (!name || name.length < 1 || name.length > 50) {
219
+ throw new Error('Wallet name must be between 1 and 50 characters.');
220
+ }
221
+ // Generate keys based on chain type
222
+ let secretKey;
223
+ let publicKey;
224
+ let address;
225
+ if (chainType === 'solana') {
226
+ const keypair = Keypair.generate();
227
+ secretKey = keypair.secretKey;
228
+ publicKey = keypair.publicKey.toBase58();
229
+ address = publicKey; // Solana address is the public key
230
+ }
231
+ else if (chainType === 'evm') {
232
+ const privateKey = generatePrivateKey();
233
+ const account = privateKeyToAccount(privateKey);
234
+ // Convert hex private key to bytes (remove 0x prefix)
235
+ secretKey = Buffer.from(privateKey.slice(2), 'hex');
236
+ publicKey = account.address; // For EVM, we store the address as public key
237
+ address = account.address;
238
+ }
239
+ else {
240
+ throw new Error(`Unsupported chain type: ${chainType}`);
241
+ }
242
+ // Generate cryptographic random values
243
+ const salt = randomBytes(SALT_LENGTH);
244
+ const nonce = randomBytes(NONCE_LENGTH);
245
+ // Derive encryption key from password
246
+ const key = await this.deriveKey(password, salt);
247
+ // Encrypt the secret key
248
+ const { ciphertext, authTag } = this.encrypt(secretKey, key, nonce);
249
+ // Create wallet file
250
+ const walletFile = {
251
+ version: 2,
252
+ chainType,
253
+ algorithm: 'aes-256-gcm',
254
+ kdf: 'argon2id',
255
+ kdfParams: {
256
+ memoryCost: ARGON2_OPTIONS.memoryCost,
257
+ timeCost: ARGON2_OPTIONS.timeCost,
258
+ parallelism: ARGON2_OPTIONS.parallelism,
259
+ },
260
+ salt: salt.toString('base64'),
261
+ nonce: nonce.toString('base64'),
262
+ authTag: authTag.toString('base64'),
263
+ ciphertext: ciphertext.toString('base64'),
264
+ publicKey,
265
+ address,
266
+ name,
267
+ createdAt: new Date().toISOString(),
268
+ updatedAt: new Date().toISOString(),
269
+ };
270
+ // Ensure directory exists and write file
271
+ await this.ensureDir();
272
+ const filePath = this.getWalletPath(name);
273
+ await fs.writeFile(filePath, JSON.stringify(walletFile, null, 2), {
274
+ encoding: 'utf-8',
275
+ mode: 0o600, // Read/write for owner only
276
+ });
277
+ // Auto-unlock after creation with auto-lock timer
278
+ if (chainType === 'solana') {
279
+ const keypair = Keypair.fromSecretKey(secretKey);
280
+ this.unlockedWallets.set(name, {
281
+ name,
282
+ chainType,
283
+ address,
284
+ publicKey,
285
+ solanaKeypair: keypair,
286
+ });
287
+ }
288
+ else {
289
+ const privateKeyHex = `0x${Buffer.from(secretKey).toString('hex')}`;
290
+ const account = privateKeyToAccount(privateKeyHex);
291
+ this.unlockedWallets.set(name, {
292
+ name,
293
+ chainType,
294
+ address,
295
+ publicKey,
296
+ evmAccount: account,
297
+ evmPrivateKey: privateKeyHex,
298
+ });
299
+ }
300
+ // Create session with auto-lock timer
301
+ const wallet = this.unlockedWallets.get(name);
302
+ const timer = setTimeout(() => this.autoLockWallet(name), this.autoLockMs);
303
+ this.walletSessions.set(name, {
304
+ wallet,
305
+ timer,
306
+ lastActivity: Date.now(),
307
+ });
308
+ return {
309
+ name,
310
+ chainType,
311
+ address,
312
+ publicKey,
313
+ filePath,
314
+ message: `Wallet "${name}" created and unlocked. Fund this address: ${address}`,
315
+ };
316
+ }
317
+ // Import existing private key
318
+ async import(name, chainType, privateKey, password) {
319
+ // Check if wallet already exists
320
+ if (await this.exists(name)) {
321
+ throw new Error(`Wallet "${name}" already exists. Use a different name.`);
322
+ }
323
+ // Validate password strength
324
+ if (password.length < 8) {
325
+ throw new Error('Password must be at least 8 characters long.');
326
+ }
327
+ // Parse private key based on chain type
328
+ let secretKey;
329
+ let publicKey;
330
+ let address;
331
+ if (chainType === 'solana') {
332
+ const keypair = this.parseSolanaPrivateKey(privateKey);
333
+ secretKey = keypair.secretKey;
334
+ publicKey = keypair.publicKey.toBase58();
335
+ address = publicKey;
336
+ }
337
+ else if (chainType === 'evm') {
338
+ const { account, privateKeyBytes } = this.parseEvmPrivateKey(privateKey);
339
+ secretKey = privateKeyBytes;
340
+ publicKey = account.address;
341
+ address = account.address;
342
+ }
343
+ else {
344
+ throw new Error(`Unsupported chain type: ${chainType}`);
345
+ }
346
+ // Generate cryptographic random values
347
+ const salt = randomBytes(SALT_LENGTH);
348
+ const nonce = randomBytes(NONCE_LENGTH);
349
+ // Derive encryption key from password
350
+ const key = await this.deriveKey(password, salt);
351
+ // Encrypt the secret key
352
+ const { ciphertext, authTag } = this.encrypt(secretKey, key, nonce);
353
+ // Create wallet file
354
+ const walletFile = {
355
+ version: 2,
356
+ chainType,
357
+ algorithm: 'aes-256-gcm',
358
+ kdf: 'argon2id',
359
+ kdfParams: {
360
+ memoryCost: ARGON2_OPTIONS.memoryCost,
361
+ timeCost: ARGON2_OPTIONS.timeCost,
362
+ parallelism: ARGON2_OPTIONS.parallelism,
363
+ },
364
+ salt: salt.toString('base64'),
365
+ nonce: nonce.toString('base64'),
366
+ authTag: authTag.toString('base64'),
367
+ ciphertext: ciphertext.toString('base64'),
368
+ publicKey,
369
+ address,
370
+ name,
371
+ createdAt: new Date().toISOString(),
372
+ updatedAt: new Date().toISOString(),
373
+ };
374
+ // Ensure directory exists and write file
375
+ await this.ensureDir();
376
+ const filePath = this.getWalletPath(name);
377
+ await fs.writeFile(filePath, JSON.stringify(walletFile, null, 2), {
378
+ encoding: 'utf-8',
379
+ mode: 0o600,
380
+ });
381
+ // Auto-unlock after import with auto-lock timer
382
+ if (chainType === 'solana') {
383
+ const keypair = Keypair.fromSecretKey(secretKey);
384
+ this.unlockedWallets.set(name, {
385
+ name,
386
+ chainType,
387
+ address,
388
+ publicKey,
389
+ solanaKeypair: keypair,
390
+ });
391
+ }
392
+ else {
393
+ const privateKeyHex = `0x${Buffer.from(secretKey).toString('hex')}`;
394
+ const account = privateKeyToAccount(privateKeyHex);
395
+ this.unlockedWallets.set(name, {
396
+ name,
397
+ chainType,
398
+ address,
399
+ publicKey,
400
+ evmAccount: account,
401
+ evmPrivateKey: privateKeyHex,
402
+ });
403
+ }
404
+ // Create session with auto-lock timer
405
+ const importedWallet = this.unlockedWallets.get(name);
406
+ const importTimer = setTimeout(() => this.autoLockWallet(name), this.autoLockMs);
407
+ this.walletSessions.set(name, {
408
+ wallet: importedWallet,
409
+ timer: importTimer,
410
+ lastActivity: Date.now(),
411
+ });
412
+ return {
413
+ name,
414
+ chainType,
415
+ address,
416
+ publicKey,
417
+ filePath,
418
+ message: `Wallet "${name}" imported and unlocked. Address: ${address}`,
419
+ };
420
+ }
421
+ // Parse Solana private key from various formats
422
+ parseSolanaPrivateKey(privateKey) {
423
+ try {
424
+ const trimmed = privateKey.trim();
425
+ // JSON array format [1,2,3,...]
426
+ if (trimmed.startsWith('[')) {
427
+ const arr = JSON.parse(trimmed);
428
+ return Keypair.fromSecretKey(Uint8Array.from(arr));
429
+ }
430
+ // Hex format
431
+ if (trimmed.startsWith('0x') || (trimmed.length === 128 && /^[0-9a-fA-F]+$/.test(trimmed))) {
432
+ const hex = trimmed.startsWith('0x') ? trimmed.slice(2) : trimmed;
433
+ return Keypair.fromSecretKey(Uint8Array.from(Buffer.from(hex, 'hex')));
434
+ }
435
+ // Base64 format
436
+ const decoded = Buffer.from(trimmed, 'base64');
437
+ if (decoded.length === 64) {
438
+ return Keypair.fromSecretKey(Uint8Array.from(decoded));
439
+ }
440
+ throw new Error('Invalid private key format');
441
+ }
442
+ catch (err) {
443
+ throw new Error(`Failed to parse Solana private key: ${err instanceof Error ? err.message : 'Invalid format'}`);
444
+ }
445
+ }
446
+ // Parse EVM private key from various formats
447
+ parseEvmPrivateKey(privateKey) {
448
+ try {
449
+ const trimmed = privateKey.trim();
450
+ let privateKeyHex;
451
+ // 0x prefixed hex
452
+ if (trimmed.startsWith('0x')) {
453
+ privateKeyHex = trimmed;
454
+ }
455
+ // Unprefixed hex (64 chars = 32 bytes)
456
+ else if (trimmed.length === 64 && /^[0-9a-fA-F]+$/.test(trimmed)) {
457
+ privateKeyHex = `0x${trimmed}`;
458
+ }
459
+ // Base64
460
+ else {
461
+ const decoded = Buffer.from(trimmed, 'base64');
462
+ if (decoded.length === 32) {
463
+ privateKeyHex = `0x${decoded.toString('hex')}`;
464
+ }
465
+ else {
466
+ throw new Error('Invalid private key length');
467
+ }
468
+ }
469
+ const account = privateKeyToAccount(privateKeyHex);
470
+ const privateKeyBytes = Buffer.from(privateKeyHex.slice(2), 'hex');
471
+ return { account, privateKeyBytes };
472
+ }
473
+ catch (err) {
474
+ throw new Error(`Failed to parse EVM private key: ${err instanceof Error ? err.message : 'Invalid format'}`);
475
+ }
476
+ }
477
+ // Unlock wallet with password
478
+ async unlock(name, password) {
479
+ // Check if wallet exists
480
+ if (!(await this.exists(name))) {
481
+ throw new Error(`Wallet "${name}" not found. Use wallet_list to see available wallets.`);
482
+ }
483
+ // Already unlocked? Refresh timer and return
484
+ if (this.unlockedWallets.has(name)) {
485
+ const wallet = this.unlockedWallets.get(name);
486
+ this.refreshAutoLock(name);
487
+ const timeoutMinutes = Math.round(this.autoLockMs / 60000);
488
+ return {
489
+ name: wallet.name,
490
+ chainType: wallet.chainType,
491
+ address: wallet.address,
492
+ message: `Wallet "${name}" is already unlocked. Timer refreshed (${timeoutMinutes} min).`,
493
+ };
494
+ }
495
+ // Read wallet file
496
+ const walletFile = await this.readWalletFile(name);
497
+ if (!walletFile) {
498
+ throw new Error('Failed to read wallet file.');
499
+ }
500
+ // Validate version and algorithm
501
+ if (walletFile.version !== 2 || walletFile.algorithm !== 'aes-256-gcm') {
502
+ throw new Error('Unsupported wallet format.');
503
+ }
504
+ // Decode values
505
+ const salt = Buffer.from(walletFile.salt, 'base64');
506
+ const nonce = Buffer.from(walletFile.nonce, 'base64');
507
+ const authTag = Buffer.from(walletFile.authTag, 'base64');
508
+ const ciphertext = Buffer.from(walletFile.ciphertext, 'base64');
509
+ // Derive key from password
510
+ const key = await this.deriveKey(password, salt);
511
+ // Decrypt
512
+ let secretKey;
513
+ try {
514
+ secretKey = this.decrypt(ciphertext, key, nonce, authTag);
515
+ }
516
+ catch {
517
+ throw new Error('Incorrect password.');
518
+ }
519
+ // Reconstruct keys based on chain type
520
+ if (walletFile.chainType === 'solana') {
521
+ const keypair = Keypair.fromSecretKey(Uint8Array.from(secretKey));
522
+ // Verify public key matches
523
+ if (keypair.publicKey.toBase58() !== walletFile.publicKey) {
524
+ throw new Error('Wallet integrity check failed.');
525
+ }
526
+ this.unlockedWallets.set(name, {
527
+ name: walletFile.name,
528
+ chainType: walletFile.chainType,
529
+ address: walletFile.address,
530
+ publicKey: walletFile.publicKey,
531
+ solanaKeypair: keypair,
532
+ });
533
+ }
534
+ else if (walletFile.chainType === 'evm') {
535
+ const privateKeyHex = `0x${secretKey.toString('hex')}`;
536
+ const account = privateKeyToAccount(privateKeyHex);
537
+ // Verify address matches
538
+ if (account.address.toLowerCase() !== walletFile.address.toLowerCase()) {
539
+ throw new Error('Wallet integrity check failed.');
540
+ }
541
+ this.unlockedWallets.set(name, {
542
+ name: walletFile.name,
543
+ chainType: walletFile.chainType,
544
+ address: walletFile.address,
545
+ publicKey: walletFile.publicKey,
546
+ evmAccount: account,
547
+ evmPrivateKey: privateKeyHex,
548
+ });
549
+ }
550
+ // Clear sensitive data from decryption
551
+ secretKey.fill(0);
552
+ // Create session with auto-lock timer
553
+ const wallet = this.unlockedWallets.get(name);
554
+ const timer = setTimeout(() => this.autoLockWallet(name), this.autoLockMs);
555
+ this.walletSessions.set(name, {
556
+ wallet,
557
+ timer,
558
+ lastActivity: Date.now(),
559
+ });
560
+ const timeoutMinutes = Math.round(this.autoLockMs / 60000);
561
+ return {
562
+ name: walletFile.name,
563
+ chainType: walletFile.chainType,
564
+ address: walletFile.address,
565
+ message: `Wallet "${name}" unlocked successfully. Auto-locks after ${timeoutMinutes} min of inactivity.`,
566
+ };
567
+ }
568
+ // Lock a specific wallet (securely wipes memory)
569
+ lock(name) {
570
+ const session = this.walletSessions.get(name);
571
+ if (session) {
572
+ clearTimeout(session.timer);
573
+ this.secureWipe(session.wallet);
574
+ this.walletSessions.delete(name);
575
+ }
576
+ if (this.unlockedWallets.has(name)) {
577
+ this.unlockedWallets.delete(name);
578
+ return true;
579
+ }
580
+ return false;
581
+ }
582
+ // Lock all wallets (securely wipes all memory)
583
+ lockAll() {
584
+ // Clear all timers and wipe memory
585
+ for (const [, session] of this.walletSessions.entries()) {
586
+ clearTimeout(session.timer);
587
+ this.secureWipe(session.wallet);
588
+ }
589
+ this.walletSessions.clear();
590
+ const count = this.unlockedWallets.size;
591
+ this.unlockedWallets.clear();
592
+ return count;
593
+ }
594
+ // Check if wallet is unlocked
595
+ isUnlocked(name) {
596
+ return this.unlockedWallets.has(name);
597
+ }
598
+ // Get session info for all unlocked wallets
599
+ getSessionInfo() {
600
+ const sessions = [];
601
+ for (const [name, session] of this.walletSessions.entries()) {
602
+ sessions.push({
603
+ name,
604
+ lastActivity: session.lastActivity,
605
+ chainType: session.wallet.chainType,
606
+ });
607
+ }
608
+ return sessions;
609
+ }
610
+ // Get unlocked wallet (throws if locked) - refreshes auto-lock timer
611
+ getUnlockedWallet(name) {
612
+ const wallet = this.unlockedWallets.get(name);
613
+ if (!wallet) {
614
+ throw new Error(`Wallet "${name}" is locked. Use wallet_unlock to unlock it first.`);
615
+ }
616
+ // Refresh auto-lock timer on access
617
+ this.refreshAutoLock(name);
618
+ return wallet;
619
+ }
620
+ // Get Solana keypair for unlocked wallet (throws if not Solana or locked)
621
+ getSolanaKeypair(name) {
622
+ const wallet = this.getUnlockedWallet(name);
623
+ if (wallet.chainType !== 'solana' || !wallet.solanaKeypair) {
624
+ throw new Error(`Wallet "${name}" is not a Solana wallet.`);
625
+ }
626
+ return wallet.solanaKeypair;
627
+ }
628
+ // Get EVM account for unlocked wallet (throws if not EVM or locked)
629
+ getEvmAccount(name) {
630
+ const wallet = this.getUnlockedWallet(name);
631
+ if (wallet.chainType !== 'evm' || !wallet.evmAccount) {
632
+ throw new Error(`Wallet "${name}" is not an EVM wallet.`);
633
+ }
634
+ return wallet.evmAccount;
635
+ }
636
+ // Get any unlocked Solana keypair (for backward compatibility)
637
+ getAnyUnlockedSolanaKeypair() {
638
+ for (const wallet of this.unlockedWallets.values()) {
639
+ if (wallet.chainType === 'solana' && wallet.solanaKeypair) {
640
+ return wallet.solanaKeypair;
641
+ }
642
+ }
643
+ return null;
644
+ }
645
+ // Get any unlocked EVM account
646
+ getAnyUnlockedEvmAccount() {
647
+ for (const wallet of this.unlockedWallets.values()) {
648
+ if (wallet.chainType === 'evm' && wallet.evmAccount) {
649
+ return wallet.evmAccount;
650
+ }
651
+ }
652
+ return null;
653
+ }
654
+ // Get any unlocked EVM private key (for SDK initialization)
655
+ getAnyUnlockedEvmPrivateKey() {
656
+ for (const wallet of this.unlockedWallets.values()) {
657
+ if (wallet.chainType === 'evm' && wallet.evmPrivateKey) {
658
+ return wallet.evmPrivateKey;
659
+ }
660
+ }
661
+ return null;
662
+ }
663
+ // Export wallet as encrypted backup
664
+ async export(name, currentPassword, exportPassword) {
665
+ // First unlock to verify password
666
+ if (!this.isUnlocked(name)) {
667
+ await this.unlock(name, currentPassword);
668
+ }
669
+ // Read current file and return it (optionally re-encrypt with new password)
670
+ const walletFile = await this.readWalletFile(name);
671
+ if (!walletFile) {
672
+ throw new Error('Failed to read wallet file.');
673
+ }
674
+ // If export password is the same or not provided, return current file
675
+ if (!exportPassword || exportPassword === currentPassword) {
676
+ return JSON.stringify(walletFile, null, 2);
677
+ }
678
+ // Re-encrypt with new password
679
+ const wallet = this.getUnlockedWallet(name);
680
+ let secretKey;
681
+ if (wallet.chainType === 'solana' && wallet.solanaKeypair) {
682
+ secretKey = wallet.solanaKeypair.secretKey;
683
+ }
684
+ else if (wallet.chainType === 'evm' && wallet.evmAccount) {
685
+ // Need to extract private key from account - not directly accessible
686
+ // We'll need to re-derive from the current encrypted state
687
+ const salt = Buffer.from(walletFile.salt, 'base64');
688
+ const nonce = Buffer.from(walletFile.nonce, 'base64');
689
+ const authTag = Buffer.from(walletFile.authTag, 'base64');
690
+ const ciphertext = Buffer.from(walletFile.ciphertext, 'base64');
691
+ const key = await this.deriveKey(currentPassword, salt);
692
+ secretKey = this.decrypt(ciphertext, key, nonce, authTag);
693
+ }
694
+ else {
695
+ throw new Error('Cannot export wallet.');
696
+ }
697
+ // Generate new salt/nonce for export
698
+ const salt = randomBytes(SALT_LENGTH);
699
+ const nonce = randomBytes(NONCE_LENGTH);
700
+ const key = await this.deriveKey(exportPassword, salt);
701
+ const { ciphertext, authTag } = this.encrypt(secretKey, key, nonce);
702
+ const exportData = {
703
+ ...walletFile,
704
+ salt: salt.toString('base64'),
705
+ nonce: nonce.toString('base64'),
706
+ authTag: authTag.toString('base64'),
707
+ ciphertext: ciphertext.toString('base64'),
708
+ updatedAt: new Date().toISOString(),
709
+ };
710
+ return JSON.stringify(exportData, null, 2);
711
+ }
712
+ // Delete wallet (requires password confirmation)
713
+ async delete(name, password) {
714
+ // Verify password first
715
+ await this.unlock(name, password);
716
+ this.lock(name);
717
+ // Delete file
718
+ await fs.unlink(this.getWalletPath(name));
719
+ }
720
+ // Change password
721
+ async changePassword(name, currentPassword, newPassword) {
722
+ // Validate new password
723
+ if (newPassword.length < 8) {
724
+ throw new Error('New password must be at least 8 characters long.');
725
+ }
726
+ // Unlock with current password
727
+ if (!this.isUnlocked(name)) {
728
+ await this.unlock(name, currentPassword);
729
+ }
730
+ const wallet = this.getUnlockedWallet(name);
731
+ const walletFile = await this.readWalletFile(name);
732
+ if (!walletFile) {
733
+ throw new Error('Failed to read wallet file.');
734
+ }
735
+ // Get secret key
736
+ let secretKey;
737
+ if (wallet.chainType === 'solana' && wallet.solanaKeypair) {
738
+ secretKey = wallet.solanaKeypair.secretKey;
739
+ }
740
+ else if (wallet.chainType === 'evm') {
741
+ const salt = Buffer.from(walletFile.salt, 'base64');
742
+ const nonce = Buffer.from(walletFile.nonce, 'base64');
743
+ const authTag = Buffer.from(walletFile.authTag, 'base64');
744
+ const ciphertext = Buffer.from(walletFile.ciphertext, 'base64');
745
+ const key = await this.deriveKey(currentPassword, salt);
746
+ secretKey = this.decrypt(ciphertext, key, nonce, authTag);
747
+ }
748
+ else {
749
+ throw new Error('Cannot change password for this wallet.');
750
+ }
751
+ // Generate new salt/nonce
752
+ const salt = randomBytes(SALT_LENGTH);
753
+ const nonce = randomBytes(NONCE_LENGTH);
754
+ const key = await this.deriveKey(newPassword, salt);
755
+ const { ciphertext, authTag } = this.encrypt(secretKey, key, nonce);
756
+ // Update wallet file
757
+ const updatedFile = {
758
+ ...walletFile,
759
+ salt: salt.toString('base64'),
760
+ nonce: nonce.toString('base64'),
761
+ authTag: authTag.toString('base64'),
762
+ ciphertext: ciphertext.toString('base64'),
763
+ updatedAt: new Date().toISOString(),
764
+ };
765
+ // Write file
766
+ await fs.writeFile(this.getWalletPath(name), JSON.stringify(updatedFile, null, 2), {
767
+ encoding: 'utf-8',
768
+ mode: 0o600,
769
+ });
770
+ }
771
+ }
772
+ // Singleton instance
773
+ let walletManagerInstance = null;
774
+ export function getWalletManager() {
775
+ if (!walletManagerInstance) {
776
+ walletManagerInstance = new WalletManager();
777
+ }
778
+ return walletManagerInstance;
779
+ }
780
+ export function setWalletManager(manager) {
781
+ walletManagerInstance = manager;
782
+ }
783
+ //# sourceMappingURL=wallet-manager.js.map