forgex-cli 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 (275) hide show
  1. package/README.md +479 -0
  2. package/dist/bin/forgex.d.ts +12 -0
  3. package/dist/bin/forgex.d.ts.map +1 -0
  4. package/dist/bin/forgex.js +23 -0
  5. package/dist/bin/forgex.js.map +1 -0
  6. package/dist/src/adapters/codex-adapter.d.ts +238 -0
  7. package/dist/src/adapters/codex-adapter.d.ts.map +1 -0
  8. package/dist/src/adapters/codex-adapter.js +524 -0
  9. package/dist/src/adapters/codex-adapter.js.map +1 -0
  10. package/dist/src/adapters/connection.d.ts +20 -0
  11. package/dist/src/adapters/connection.d.ts.map +1 -0
  12. package/dist/src/adapters/connection.js +43 -0
  13. package/dist/src/adapters/connection.js.map +1 -0
  14. package/dist/src/adapters/ipfs.d.ts +17 -0
  15. package/dist/src/adapters/ipfs.d.ts.map +1 -0
  16. package/dist/src/adapters/ipfs.js +30 -0
  17. package/dist/src/adapters/ipfs.js.map +1 -0
  18. package/dist/src/adapters/jito-adapter.d.ts +194 -0
  19. package/dist/src/adapters/jito-adapter.d.ts.map +1 -0
  20. package/dist/src/adapters/jito-adapter.js +474 -0
  21. package/dist/src/adapters/jito-adapter.js.map +1 -0
  22. package/dist/src/adapters/rpc-adapter.d.ts +148 -0
  23. package/dist/src/adapters/rpc-adapter.d.ts.map +1 -0
  24. package/dist/src/adapters/rpc-adapter.js +410 -0
  25. package/dist/src/adapters/rpc-adapter.js.map +1 -0
  26. package/dist/src/adapters/sdk-adapter.d.ts +148 -0
  27. package/dist/src/adapters/sdk-adapter.d.ts.map +1 -0
  28. package/dist/src/adapters/sdk-adapter.js +554 -0
  29. package/dist/src/adapters/sdk-adapter.js.map +1 -0
  30. package/dist/src/commands/config/index.d.ts +8 -0
  31. package/dist/src/commands/config/index.d.ts.map +1 -0
  32. package/dist/src/commands/config/index.js +91 -0
  33. package/dist/src/commands/config/index.js.map +1 -0
  34. package/dist/src/commands/query/index.d.ts +10 -0
  35. package/dist/src/commands/query/index.d.ts.map +1 -0
  36. package/dist/src/commands/query/index.js +376 -0
  37. package/dist/src/commands/query/index.js.map +1 -0
  38. package/dist/src/commands/sniper/index.d.ts +16 -0
  39. package/dist/src/commands/sniper/index.d.ts.map +1 -0
  40. package/dist/src/commands/sniper/index.js +292 -0
  41. package/dist/src/commands/sniper/index.js.map +1 -0
  42. package/dist/src/commands/token/index.d.ts +16 -0
  43. package/dist/src/commands/token/index.d.ts.map +1 -0
  44. package/dist/src/commands/token/index.js +295 -0
  45. package/dist/src/commands/token/index.js.map +1 -0
  46. package/dist/src/commands/tools/index.d.ts +17 -0
  47. package/dist/src/commands/tools/index.d.ts.map +1 -0
  48. package/dist/src/commands/tools/index.js +626 -0
  49. package/dist/src/commands/tools/index.js.map +1 -0
  50. package/dist/src/commands/trade/index.d.ts +8 -0
  51. package/dist/src/commands/trade/index.d.ts.map +1 -0
  52. package/dist/src/commands/trade/index.js +531 -0
  53. package/dist/src/commands/trade/index.js.map +1 -0
  54. package/dist/src/commands/transfer/index.d.ts +16 -0
  55. package/dist/src/commands/transfer/index.d.ts.map +1 -0
  56. package/dist/src/commands/transfer/index.js +509 -0
  57. package/dist/src/commands/transfer/index.js.map +1 -0
  58. package/dist/src/commands/wallet/index.d.ts +10 -0
  59. package/dist/src/commands/wallet/index.d.ts.map +1 -0
  60. package/dist/src/commands/wallet/index.js +828 -0
  61. package/dist/src/commands/wallet/index.js.map +1 -0
  62. package/dist/src/config.d.ts +74 -0
  63. package/dist/src/config.d.ts.map +1 -0
  64. package/dist/src/config.js +190 -0
  65. package/dist/src/config.js.map +1 -0
  66. package/dist/src/const/IDL/index.d.ts +4 -0
  67. package/dist/src/const/IDL/index.d.ts.map +1 -0
  68. package/dist/src/const/IDL/index.js +2 -0
  69. package/dist/src/const/IDL/index.js.map +1 -0
  70. package/dist/src/const/IDL/meteora-DLMM.d.ts +10249 -0
  71. package/dist/src/const/IDL/meteora-DLMM.d.ts.map +1 -0
  72. package/dist/src/const/IDL/meteora-DLMM.js +4177 -0
  73. package/dist/src/const/IDL/meteora-DLMM.js.map +1 -0
  74. package/dist/src/const/IDL/pump-fun.d.ts +4811 -0
  75. package/dist/src/const/IDL/pump-fun.d.ts.map +1 -0
  76. package/dist/src/const/IDL/pump-fun.js +2954 -0
  77. package/dist/src/const/IDL/pump-fun.js.map +1 -0
  78. package/dist/src/const/IDL/pump-swap-IDL.d.ts +3119 -0
  79. package/dist/src/const/IDL/pump-swap-IDL.d.ts.map +1 -0
  80. package/dist/src/const/IDL/pump-swap-IDL.js +2095 -0
  81. package/dist/src/const/IDL/pump-swap-IDL.js.map +1 -0
  82. package/dist/src/const/IDL/raydium-launchlab-IDL.d.ts +4031 -0
  83. package/dist/src/const/IDL/raydium-launchlab-IDL.d.ts.map +1 -0
  84. package/dist/src/const/IDL/raydium-launchlab-IDL.js +2110 -0
  85. package/dist/src/const/IDL/raydium-launchlab-IDL.js.map +1 -0
  86. package/dist/src/const/index.d.ts +40 -0
  87. package/dist/src/const/index.d.ts.map +1 -0
  88. package/dist/src/const/index.js +57 -0
  89. package/dist/src/const/index.js.map +1 -0
  90. package/dist/src/data-source.d.ts +270 -0
  91. package/dist/src/data-source.d.ts.map +1 -0
  92. package/dist/src/data-source.js +628 -0
  93. package/dist/src/data-source.js.map +1 -0
  94. package/dist/src/data-store/index.d.ts +87 -0
  95. package/dist/src/data-store/index.d.ts.map +1 -0
  96. package/dist/src/data-store/index.js +561 -0
  97. package/dist/src/data-store/index.js.map +1 -0
  98. package/dist/src/data-store/types.d.ts +91 -0
  99. package/dist/src/data-store/types.d.ts.map +1 -0
  100. package/dist/src/data-store/types.js +8 -0
  101. package/dist/src/data-store/types.js.map +1 -0
  102. package/dist/src/index.d.ts +8 -0
  103. package/dist/src/index.d.ts.map +1 -0
  104. package/dist/src/index.js +36 -0
  105. package/dist/src/index.js.map +1 -0
  106. package/dist/src/output.d.ts +52 -0
  107. package/dist/src/output.d.ts.map +1 -0
  108. package/dist/src/output.js +218 -0
  109. package/dist/src/output.js.map +1 -0
  110. package/dist/src/shims/store.d.ts +58 -0
  111. package/dist/src/shims/store.d.ts.map +1 -0
  112. package/dist/src/shims/store.js +55 -0
  113. package/dist/src/shims/store.js.map +1 -0
  114. package/dist/src/sol-sdk/account/index.d.ts +13 -0
  115. package/dist/src/sol-sdk/account/index.d.ts.map +1 -0
  116. package/dist/src/sol-sdk/account/index.js +174 -0
  117. package/dist/src/sol-sdk/account/index.js.map +1 -0
  118. package/dist/src/sol-sdk/account/lookupTable.d.ts +24 -0
  119. package/dist/src/sol-sdk/account/lookupTable.d.ts.map +1 -0
  120. package/dist/src/sol-sdk/account/lookupTable.js +103 -0
  121. package/dist/src/sol-sdk/account/lookupTable.js.map +1 -0
  122. package/dist/src/sol-sdk/batch/create.d.ts +23 -0
  123. package/dist/src/sol-sdk/batch/create.d.ts.map +1 -0
  124. package/dist/src/sol-sdk/batch/create.js +580 -0
  125. package/dist/src/sol-sdk/batch/create.js.map +1 -0
  126. package/dist/src/sol-sdk/batch/external-sniper.d.ts +31 -0
  127. package/dist/src/sol-sdk/batch/external-sniper.d.ts.map +1 -0
  128. package/dist/src/sol-sdk/batch/external-sniper.js +163 -0
  129. package/dist/src/sol-sdk/batch/external-sniper.js.map +1 -0
  130. package/dist/src/sol-sdk/batch/index.d.ts +144 -0
  131. package/dist/src/sol-sdk/batch/index.d.ts.map +1 -0
  132. package/dist/src/sol-sdk/batch/index.js +1573 -0
  133. package/dist/src/sol-sdk/batch/index.js.map +1 -0
  134. package/dist/src/sol-sdk/calc.d.ts +277 -0
  135. package/dist/src/sol-sdk/calc.d.ts.map +1 -0
  136. package/dist/src/sol-sdk/calc.js +590 -0
  137. package/dist/src/sol-sdk/calc.js.map +1 -0
  138. package/dist/src/sol-sdk/index.d.ts +1 -0
  139. package/dist/src/sol-sdk/index.d.ts.map +1 -0
  140. package/dist/src/sol-sdk/index.js +1 -0
  141. package/dist/src/sol-sdk/index.js.map +1 -0
  142. package/dist/src/sol-sdk/jito/index.d.ts +50 -0
  143. package/dist/src/sol-sdk/jito/index.d.ts.map +1 -0
  144. package/dist/src/sol-sdk/jito/index.js +225 -0
  145. package/dist/src/sol-sdk/jito/index.js.map +1 -0
  146. package/dist/src/sol-sdk/launchlab/index.d.ts +32 -0
  147. package/dist/src/sol-sdk/launchlab/index.d.ts.map +1 -0
  148. package/dist/src/sol-sdk/launchlab/index.js +78 -0
  149. package/dist/src/sol-sdk/launchlab/index.js.map +1 -0
  150. package/dist/src/sol-sdk/launchlab/instructions/buy.d.ts +27 -0
  151. package/dist/src/sol-sdk/launchlab/instructions/buy.d.ts.map +1 -0
  152. package/dist/src/sol-sdk/launchlab/instructions/buy.js +124 -0
  153. package/dist/src/sol-sdk/launchlab/instructions/buy.js.map +1 -0
  154. package/dist/src/sol-sdk/launchlab/instructions/create.d.ts +27 -0
  155. package/dist/src/sol-sdk/launchlab/instructions/create.d.ts.map +1 -0
  156. package/dist/src/sol-sdk/launchlab/instructions/create.js +125 -0
  157. package/dist/src/sol-sdk/launchlab/instructions/create.js.map +1 -0
  158. package/dist/src/sol-sdk/launchlab/instructions/sell.d.ts +15 -0
  159. package/dist/src/sol-sdk/launchlab/instructions/sell.d.ts.map +1 -0
  160. package/dist/src/sol-sdk/launchlab/instructions/sell.js +65 -0
  161. package/dist/src/sol-sdk/launchlab/instructions/sell.js.map +1 -0
  162. package/dist/src/sol-sdk/meteora/index.d.ts +32 -0
  163. package/dist/src/sol-sdk/meteora/index.d.ts.map +1 -0
  164. package/dist/src/sol-sdk/meteora/index.js +72 -0
  165. package/dist/src/sol-sdk/meteora/index.js.map +1 -0
  166. package/dist/src/sol-sdk/meteora/instructions/buy.d.ts +46 -0
  167. package/dist/src/sol-sdk/meteora/instructions/buy.d.ts.map +1 -0
  168. package/dist/src/sol-sdk/meteora/instructions/buy.js +153 -0
  169. package/dist/src/sol-sdk/meteora/instructions/buy.js.map +1 -0
  170. package/dist/src/sol-sdk/meteora/instructions/sell.d.ts +24 -0
  171. package/dist/src/sol-sdk/meteora/instructions/sell.d.ts.map +1 -0
  172. package/dist/src/sol-sdk/meteora/instructions/sell.js +98 -0
  173. package/dist/src/sol-sdk/meteora/instructions/sell.js.map +1 -0
  174. package/dist/src/sol-sdk/pump/index.d.ts +22 -0
  175. package/dist/src/sol-sdk/pump/index.d.ts.map +1 -0
  176. package/dist/src/sol-sdk/pump/index.js +101 -0
  177. package/dist/src/sol-sdk/pump/index.js.map +1 -0
  178. package/dist/src/sol-sdk/pump/instructions/buy.d.ts +29 -0
  179. package/dist/src/sol-sdk/pump/instructions/buy.d.ts.map +1 -0
  180. package/dist/src/sol-sdk/pump/instructions/buy.js +131 -0
  181. package/dist/src/sol-sdk/pump/instructions/buy.js.map +1 -0
  182. package/dist/src/sol-sdk/pump/instructions/createAndBuy.d.ts +36 -0
  183. package/dist/src/sol-sdk/pump/instructions/createAndBuy.d.ts.map +1 -0
  184. package/dist/src/sol-sdk/pump/instructions/createAndBuy.js +77 -0
  185. package/dist/src/sol-sdk/pump/instructions/createAndBuy.js.map +1 -0
  186. package/dist/src/sol-sdk/pump/instructions/sell.d.ts +7 -0
  187. package/dist/src/sol-sdk/pump/instructions/sell.d.ts.map +1 -0
  188. package/dist/src/sol-sdk/pump/instructions/sell.js +38 -0
  189. package/dist/src/sol-sdk/pump/instructions/sell.js.map +1 -0
  190. package/dist/src/sol-sdk/pumpswap/index.d.ts +9 -0
  191. package/dist/src/sol-sdk/pumpswap/index.d.ts.map +1 -0
  192. package/dist/src/sol-sdk/pumpswap/index.js +27 -0
  193. package/dist/src/sol-sdk/pumpswap/index.js.map +1 -0
  194. package/dist/src/sol-sdk/pumpswap/instructions/buy.d.ts +61 -0
  195. package/dist/src/sol-sdk/pumpswap/instructions/buy.d.ts.map +1 -0
  196. package/dist/src/sol-sdk/pumpswap/instructions/buy.js +215 -0
  197. package/dist/src/sol-sdk/pumpswap/instructions/buy.js.map +1 -0
  198. package/dist/src/sol-sdk/pumpswap/instructions/migrate.d.ts +3 -0
  199. package/dist/src/sol-sdk/pumpswap/instructions/migrate.d.ts.map +1 -0
  200. package/dist/src/sol-sdk/pumpswap/instructions/migrate.js +33 -0
  201. package/dist/src/sol-sdk/pumpswap/instructions/migrate.js.map +1 -0
  202. package/dist/src/sol-sdk/pumpswap/instructions/sell.d.ts +20 -0
  203. package/dist/src/sol-sdk/pumpswap/instructions/sell.d.ts.map +1 -0
  204. package/dist/src/sol-sdk/pumpswap/instructions/sell.js +107 -0
  205. package/dist/src/sol-sdk/pumpswap/instructions/sell.js.map +1 -0
  206. package/dist/src/sol-sdk/pumpswap/rpc/index.d.ts +28 -0
  207. package/dist/src/sol-sdk/pumpswap/rpc/index.d.ts.map +1 -0
  208. package/dist/src/sol-sdk/pumpswap/rpc/index.js +63 -0
  209. package/dist/src/sol-sdk/pumpswap/rpc/index.js.map +1 -0
  210. package/dist/src/sol-sdk/raydium/index.d.ts +16 -0
  211. package/dist/src/sol-sdk/raydium/index.d.ts.map +1 -0
  212. package/dist/src/sol-sdk/raydium/index.js +47 -0
  213. package/dist/src/sol-sdk/raydium/index.js.map +1 -0
  214. package/dist/src/sol-sdk/raydium/instructions/buy.d.ts +29 -0
  215. package/dist/src/sol-sdk/raydium/instructions/buy.d.ts.map +1 -0
  216. package/dist/src/sol-sdk/raydium/instructions/buy.js +106 -0
  217. package/dist/src/sol-sdk/raydium/instructions/buy.js.map +1 -0
  218. package/dist/src/sol-sdk/raydium/instructions/cpmmBuy.d.ts +29 -0
  219. package/dist/src/sol-sdk/raydium/instructions/cpmmBuy.d.ts.map +1 -0
  220. package/dist/src/sol-sdk/raydium/instructions/cpmmBuy.js +80 -0
  221. package/dist/src/sol-sdk/raydium/instructions/cpmmBuy.js.map +1 -0
  222. package/dist/src/sol-sdk/raydium/instructions/cpmmSell.d.ts +17 -0
  223. package/dist/src/sol-sdk/raydium/instructions/cpmmSell.d.ts.map +1 -0
  224. package/dist/src/sol-sdk/raydium/instructions/cpmmSell.js +56 -0
  225. package/dist/src/sol-sdk/raydium/instructions/cpmmSell.js.map +1 -0
  226. package/dist/src/sol-sdk/raydium/instructions/sell.d.ts +8558 -0
  227. package/dist/src/sol-sdk/raydium/instructions/sell.d.ts.map +1 -0
  228. package/dist/src/sol-sdk/raydium/instructions/sell.js +70 -0
  229. package/dist/src/sol-sdk/raydium/instructions/sell.js.map +1 -0
  230. package/dist/src/sol-sdk/raydium/rpc/index.d.ts +39 -0
  231. package/dist/src/sol-sdk/raydium/rpc/index.d.ts.map +1 -0
  232. package/dist/src/sol-sdk/raydium/rpc/index.js +82 -0
  233. package/dist/src/sol-sdk/raydium/rpc/index.js.map +1 -0
  234. package/dist/src/sol-sdk/raydium/rpc/raydium.d.ts +5 -0
  235. package/dist/src/sol-sdk/raydium/rpc/raydium.d.ts.map +1 -0
  236. package/dist/src/sol-sdk/raydium/rpc/raydium.js +18 -0
  237. package/dist/src/sol-sdk/raydium/rpc/raydium.js.map +1 -0
  238. package/dist/src/sol-sdk/rpc/index.d.ts +33 -0
  239. package/dist/src/sol-sdk/rpc/index.d.ts.map +1 -0
  240. package/dist/src/sol-sdk/rpc/index.js +128 -0
  241. package/dist/src/sol-sdk/rpc/index.js.map +1 -0
  242. package/dist/src/sol-sdk/transfer/index.d.ts +24 -0
  243. package/dist/src/sol-sdk/transfer/index.d.ts.map +1 -0
  244. package/dist/src/sol-sdk/transfer/index.js +65 -0
  245. package/dist/src/sol-sdk/transfer/index.js.map +1 -0
  246. package/dist/src/sol-sdk/turnover/index.d.ts +84 -0
  247. package/dist/src/sol-sdk/turnover/index.d.ts.map +1 -0
  248. package/dist/src/sol-sdk/turnover/index.js +569 -0
  249. package/dist/src/sol-sdk/turnover/index.js.map +1 -0
  250. package/dist/src/tx-tracker/detail-adapter.d.ts +100 -0
  251. package/dist/src/tx-tracker/detail-adapter.d.ts.map +1 -0
  252. package/dist/src/tx-tracker/detail-adapter.js +215 -0
  253. package/dist/src/tx-tracker/detail-adapter.js.map +1 -0
  254. package/dist/src/tx-tracker/index.d.ts +142 -0
  255. package/dist/src/tx-tracker/index.d.ts.map +1 -0
  256. package/dist/src/tx-tracker/index.js +447 -0
  257. package/dist/src/tx-tracker/index.js.map +1 -0
  258. package/dist/src/types/index.d.ts +76 -0
  259. package/dist/src/types/index.d.ts.map +1 -0
  260. package/dist/src/types/index.js +69 -0
  261. package/dist/src/types/index.js.map +1 -0
  262. package/dist/src/types/websocket.d.ts +15 -0
  263. package/dist/src/types/websocket.d.ts.map +1 -0
  264. package/dist/src/types/websocket.js +18 -0
  265. package/dist/src/types/websocket.js.map +1 -0
  266. package/dist/src/utils/index.d.ts +13 -0
  267. package/dist/src/utils/index.d.ts.map +1 -0
  268. package/dist/src/utils/index.js +174 -0
  269. package/dist/src/utils/index.js.map +1 -0
  270. package/dist/src/wallet-store.d.ts +124 -0
  271. package/dist/src/wallet-store.d.ts.map +1 -0
  272. package/dist/src/wallet-store.js +524 -0
  273. package/dist/src/wallet-store.js.map +1 -0
  274. package/package.json +86 -0
  275. package/scripts/postinstall.js +88 -0
@@ -0,0 +1,828 @@
1
+ /**
2
+ * 钱包组管理命令组
3
+ *
4
+ * forgex wallet create-group | list-groups | group-info | delete-group |
5
+ * generate | add | remove | import | export |
6
+ * import-group | export-group | overview | grind
7
+ */
8
+ import { execSync, spawn } from 'child_process';
9
+ import path from 'path';
10
+ import fs from 'fs';
11
+ import { getAllGroups, getGroup, saveGroup, removeGroup, addWalletsToGroup, removeWalletsFromGroup, generateWallets, walletFromPrivateKey, exportGroupToCsv, importWalletsFromCsv, exportAllGroupsJson, exportEncryptedGroupsJson, importGroupsFromJson, ensurePasswordAndValidate, getDecryptedPrivateKey, } from '../../wallet-store.js';
12
+ import { getDataSource } from '../../data-source.js';
13
+ import { VANITY_DIR, ensureConfigDir } from '../../config.js';
14
+ import { output, success, error, warn, getOutputFormat } from '../../output.js';
15
+ export function registerWalletCommands(program) {
16
+ const walletCmd = program
17
+ .command('wallet')
18
+ .description('钱包组管理');
19
+ // ============================================================
20
+ // forgex wallet create-group
21
+ // ============================================================
22
+ walletCmd
23
+ .command('create-group')
24
+ .description('创建钱包组')
25
+ .requiredOption('--name <name>', '钱包组名称')
26
+ .option('--type <type>', '类型: local | monitor', 'local')
27
+ .option('--remark <remark>', '备注', '')
28
+ .option('--monitor-type <type>', '监控类型: normal | top100 | retail', 'normal')
29
+ .option('--monitor-ca <ca>', '监控代币 CA')
30
+ .option('--filter-group-ids <ids>', '过滤钱包组 ID(逗号分隔)')
31
+ .action(async (options) => {
32
+ try {
33
+ // 验证 --type 参数
34
+ if (options.type !== 'local' && options.type !== 'monitor') {
35
+ error(`无效的类型 "${options.type}",只支持 local 或 monitor`);
36
+ process.exit(1);
37
+ }
38
+ const groupType = options.type;
39
+ // 验证 --monitor-type 参数
40
+ const validMonitorTypes = ['normal', 'top100', 'retail'];
41
+ if (!validMonitorTypes.includes(options.monitorType)) {
42
+ error(`无效的监控类型 "${options.monitorType}",只支持 normal、top100 或 retail`);
43
+ process.exit(1);
44
+ }
45
+ const monitorType = options.monitorType;
46
+ const filterGroupIds = options.filterGroupIds
47
+ ? options.filterGroupIds.split(',')
48
+ : [];
49
+ // 验证 filterGroupIds 中的值是否为有效数字
50
+ const filterGroupIdsNum = filterGroupIds.map(Number);
51
+ if (filterGroupIdsNum.some(isNaN)) {
52
+ error('--filter-group-ids 包含无效的 ID,请使用逗号分隔的数字');
53
+ process.exit(1);
54
+ }
55
+ // 纯本地操作: 生成本地 group ID
56
+ const existingGroups = getAllGroups();
57
+ const maxId = existingGroups.reduce((max, g) => Math.max(max, g.groupId), 0);
58
+ const groupId = maxId + 1;
59
+ // 本地存储
60
+ const newGroup = {
61
+ name: options.name,
62
+ groupId,
63
+ groupType,
64
+ wallets: [],
65
+ monitorType,
66
+ note: options.remark,
67
+ monitorCA: options.monitorCa,
68
+ filterGroupIds: filterGroupIdsNum,
69
+ };
70
+ saveGroup(newGroup);
71
+ output({
72
+ success: true,
73
+ groupId: newGroup.groupId,
74
+ name: newGroup.name,
75
+ type: newGroup.groupType,
76
+ });
77
+ }
78
+ catch (e) {
79
+ error('创建钱包组失败', e.message);
80
+ process.exit(1);
81
+ }
82
+ });
83
+ // ============================================================
84
+ // forgex wallet list-groups
85
+ // ============================================================
86
+ walletCmd
87
+ .command('list-groups')
88
+ .description('列出所有钱包组')
89
+ .option('--type <type>', '按类型过滤: local | monitor')
90
+ .action((options) => {
91
+ try {
92
+ // 验证 --type 参数
93
+ if (options.type && options.type !== 'local' && options.type !== 'monitor') {
94
+ error(`无效的类型 "${options.type}",只支持 local 或 monitor`);
95
+ process.exit(1);
96
+ }
97
+ let groups = getAllGroups();
98
+ if (options.type) {
99
+ groups = groups.filter(g => g.groupType === options.type);
100
+ }
101
+ if (groups.length === 0) {
102
+ const filterMsg = options.type ? `(类型: ${options.type})` : '';
103
+ warn(`没有找到钱包组${filterMsg}`);
104
+ }
105
+ const result = groups.map(g => ({
106
+ groupId: g.groupId,
107
+ name: g.name,
108
+ type: g.groupType,
109
+ walletCount: g.wallets.length,
110
+ monitorType: g.monitorType,
111
+ note: g.note,
112
+ }));
113
+ output(result, {
114
+ columns: [
115
+ { key: 'groupId', header: 'ID' },
116
+ { key: 'name', header: '名称' },
117
+ { key: 'type', header: '类型' },
118
+ { key: 'walletCount', header: '钱包数' },
119
+ { key: 'monitorType', header: '监控类型' },
120
+ { key: 'note', header: '备注' },
121
+ ],
122
+ });
123
+ }
124
+ catch (e) {
125
+ error('获取钱包组列表失败', e.message);
126
+ process.exit(1);
127
+ }
128
+ });
129
+ // ============================================================
130
+ // forgex wallet group-info
131
+ // ============================================================
132
+ walletCmd
133
+ .command('group-info')
134
+ .description('查看钱包组详情')
135
+ .requiredOption('--id <groupId>', '钱包组 ID')
136
+ .option('--show-keys', '显示私钥(危险操作)', false)
137
+ .action(async (options) => {
138
+ try {
139
+ const groupId = Number(options.id);
140
+ if (isNaN(groupId)) {
141
+ error(`无效的钱包组 ID "${options.id}",请提供数字 ID`);
142
+ process.exit(1);
143
+ }
144
+ // 如果需要显示私钥,先确保密码已设置且正确
145
+ if (options.showKeys) {
146
+ warn('警告:即将显示私钥明文,请确保当前环境安全,切勿在公共场所或共享屏幕时使用此命令');
147
+ await ensurePasswordAndValidate();
148
+ }
149
+ const group = getGroup(groupId);
150
+ if (!group) {
151
+ error(`钱包组 ${groupId} 不存在`);
152
+ process.exit(1);
153
+ }
154
+ const walletsList = group.wallets.map(w => {
155
+ const entry = {
156
+ address: w.walletAddress,
157
+ note: w.note,
158
+ };
159
+ if (options.showKeys) {
160
+ entry.privateKey = getDecryptedPrivateKey(w);
161
+ }
162
+ return entry;
163
+ });
164
+ const result = {
165
+ groupId: group.groupId,
166
+ name: group.name,
167
+ type: group.groupType,
168
+ walletCount: group.wallets.length,
169
+ monitorType: group.monitorType,
170
+ note: group.note,
171
+ wallets: walletsList,
172
+ };
173
+ output(result);
174
+ }
175
+ catch (e) {
176
+ error('获取钱包组详情失败', e.message);
177
+ process.exit(1);
178
+ }
179
+ });
180
+ // ============================================================
181
+ // forgex wallet delete-group
182
+ // ============================================================
183
+ walletCmd
184
+ .command('delete-group')
185
+ .description('删除钱包组')
186
+ .requiredOption('--id <groupId>', '钱包组 ID')
187
+ .option('--force', '跳过确认', false)
188
+ .action(async (options) => {
189
+ try {
190
+ const groupId = Number(options.id);
191
+ if (isNaN(groupId)) {
192
+ error(`无效的钱包组 ID "${options.id}",请提供数字 ID`);
193
+ process.exit(1);
194
+ }
195
+ // 检查本地是否存在该钱包组
196
+ const group = getGroup(groupId);
197
+ if (!group) {
198
+ error(`钱包组 ${groupId} 不存在`);
199
+ process.exit(1);
200
+ }
201
+ // 非 --force 模式下,提示用户确认
202
+ if (!options.force) {
203
+ const { confirm } = await import('@inquirer/prompts');
204
+ const confirmed = await confirm({
205
+ message: `确认删除钱包组 "${group.name}" (ID: ${groupId}, ${group.wallets.length} 个钱包)?此操作不可恢复。`,
206
+ default: false,
207
+ });
208
+ if (!confirmed) {
209
+ warn('已取消删除操作');
210
+ return;
211
+ }
212
+ }
213
+ // 纯本地删除
214
+ removeGroup(groupId);
215
+ output({ success: true, groupId, message: '钱包组已删除' });
216
+ }
217
+ catch (e) {
218
+ error('删除钱包组失败', e.message);
219
+ process.exit(1);
220
+ }
221
+ });
222
+ // ============================================================
223
+ // forgex wallet generate
224
+ // ============================================================
225
+ walletCmd
226
+ .command('generate')
227
+ .description('生成新钱包')
228
+ .requiredOption('--group <groupId>', '目标钱包组 ID')
229
+ .option('--count <n>', '生成数量', '1')
230
+ .action(async (options) => {
231
+ try {
232
+ await ensurePasswordAndValidate();
233
+ const groupId = Number(options.group);
234
+ if (isNaN(groupId)) {
235
+ error(`无效的钱包组 ID "${options.group}",请提供数字 ID`);
236
+ process.exit(1);
237
+ }
238
+ const count = Number(options.count);
239
+ if (isNaN(count) || !Number.isInteger(count) || count <= 0) {
240
+ error(`无效的生成数量 "${options.count}",请提供正整数`);
241
+ process.exit(1);
242
+ }
243
+ const group = getGroup(groupId);
244
+ if (!group) {
245
+ error(`钱包组 ${groupId} 不存在`);
246
+ process.exit(1);
247
+ }
248
+ if (group.wallets.length + count > 100) {
249
+ error(`钱包数量超限:当前 ${group.wallets.length} 个,最多再生成 ${100 - group.wallets.length} 个(上限 100)`);
250
+ process.exit(1);
251
+ }
252
+ const wallets = generateWallets(count);
253
+ // 保存明文私钥用于输出(addWalletsToGroup 可能会加密 wallets 中的 privateKey)
254
+ const plaintextKeys = wallets.map(w => ({
255
+ address: w.walletAddress,
256
+ privateKey: w.privateKey,
257
+ }));
258
+ // 纯本地存储(如果存储已加密,此函数会加密 wallets 的 privateKey 字段)
259
+ addWalletsToGroup(groupId, wallets);
260
+ // Bug 1 fix: JSON 保持原结构; table/minimal 用 columns 展示生成的钱包列表
261
+ const fmt = getOutputFormat();
262
+ if (fmt === 'json') {
263
+ output({
264
+ success: true,
265
+ groupId,
266
+ generated: plaintextKeys,
267
+ });
268
+ }
269
+ else {
270
+ success(`已生成 ${plaintextKeys.length} 个钱包到组 ${groupId}`);
271
+ output(plaintextKeys.map((w, i) => ({
272
+ index: i + 1,
273
+ address: w.address,
274
+ privateKey: w.privateKey,
275
+ })), {
276
+ columns: [
277
+ { key: 'index', header: '#' },
278
+ { key: 'address', header: '地址' },
279
+ { key: 'privateKey', header: '私钥' },
280
+ ],
281
+ });
282
+ }
283
+ }
284
+ catch (e) {
285
+ error('生成钱包失败', e.message);
286
+ process.exit(1);
287
+ }
288
+ });
289
+ // ============================================================
290
+ // forgex wallet add
291
+ // ============================================================
292
+ walletCmd
293
+ .command('add')
294
+ .description('添加钱包到钱包组')
295
+ .requiredOption('--group <groupId>', '目标钱包组 ID')
296
+ .requiredOption('--private-key <key>', '私钥(Base58 编码)')
297
+ .option('--note <note>', '备注', '')
298
+ .action(async (options) => {
299
+ try {
300
+ await ensurePasswordAndValidate();
301
+ const groupId = Number(options.group);
302
+ if (isNaN(groupId)) {
303
+ error(`无效的钱包组 ID "${options.group}",请提供数字 ID`);
304
+ process.exit(1);
305
+ }
306
+ // 验证钱包组存在
307
+ const group = getGroup(groupId);
308
+ if (!group) {
309
+ error(`钱包组 ${groupId} 不存在`);
310
+ process.exit(1);
311
+ }
312
+ // 验证私钥有效性
313
+ let wallet;
314
+ try {
315
+ wallet = walletFromPrivateKey(options.privateKey, options.note);
316
+ }
317
+ catch {
318
+ error('无效的私钥:请提供有效的 Base58 编码私钥');
319
+ process.exit(1);
320
+ }
321
+ // 检查地址是否已存在于该组
322
+ const exists = group.wallets.some(w => w.walletAddress === wallet.walletAddress);
323
+ if (exists) {
324
+ warn(`钱包 ${wallet.walletAddress} 已存在于钱包组 ${groupId} 中,跳过重复添加`);
325
+ output({
326
+ success: true,
327
+ groupId,
328
+ address: wallet.walletAddress,
329
+ duplicate: true,
330
+ });
331
+ return;
332
+ }
333
+ // 纯本地存储
334
+ addWalletsToGroup(groupId, [wallet]);
335
+ output({
336
+ success: true,
337
+ groupId,
338
+ address: wallet.walletAddress,
339
+ });
340
+ }
341
+ catch (e) {
342
+ error('添加钱包失败', e.message);
343
+ process.exit(1);
344
+ }
345
+ });
346
+ // ============================================================
347
+ // forgex wallet remove
348
+ // ============================================================
349
+ walletCmd
350
+ .command('remove')
351
+ .description('从钱包组移除钱包')
352
+ .requiredOption('--group <groupId>', '目标钱包组 ID')
353
+ .requiredOption('--address <addr>', '钱包地址')
354
+ .action(async (options) => {
355
+ try {
356
+ const groupId = Number(options.group);
357
+ if (isNaN(groupId)) {
358
+ error(`无效的钱包组 ID "${options.group}",请提供数字 ID`);
359
+ process.exit(1);
360
+ }
361
+ // 验证钱包组存在
362
+ const group = getGroup(groupId);
363
+ if (!group) {
364
+ error(`钱包组 ${groupId} 不存在`);
365
+ process.exit(1);
366
+ }
367
+ // 验证地址是否存在于该钱包组中
368
+ const walletExists = group.wallets.some(w => w.walletAddress === options.address);
369
+ if (!walletExists) {
370
+ error(`钱包 ${options.address} 不在钱包组 ${groupId} 中`);
371
+ process.exit(1);
372
+ }
373
+ // 纯本地移除
374
+ removeWalletsFromGroup(groupId, [options.address]);
375
+ output({
376
+ success: true,
377
+ groupId,
378
+ removed: options.address,
379
+ });
380
+ }
381
+ catch (e) {
382
+ error('移除钱包失败', e.message);
383
+ process.exit(1);
384
+ }
385
+ });
386
+ // ============================================================
387
+ // forgex wallet import
388
+ // ============================================================
389
+ walletCmd
390
+ .command('import')
391
+ .description('从 CSV 文件导入钱包')
392
+ .requiredOption('--group <groupId>', '目标钱包组 ID')
393
+ .requiredOption('--file <csvPath>', 'CSV 文件路径')
394
+ .action(async (options) => {
395
+ try {
396
+ await ensurePasswordAndValidate();
397
+ const groupId = Number(options.group);
398
+ if (isNaN(groupId) || !Number.isInteger(groupId) || groupId <= 0) {
399
+ error(`无效的钱包组 ID "${options.group}",请提供正整数`);
400
+ process.exit(1);
401
+ }
402
+ // 验证钱包组存在
403
+ const group = getGroup(groupId);
404
+ if (!group) {
405
+ error(`钱包组 ${groupId} 不存在`);
406
+ process.exit(1);
407
+ }
408
+ // 验证文件存在
409
+ if (!fs.existsSync(options.file)) {
410
+ error(`CSV 文件不存在: ${options.file}`);
411
+ process.exit(1);
412
+ }
413
+ const csvContent = fs.readFileSync(options.file, 'utf-8');
414
+ const wallets = importWalletsFromCsv(csvContent);
415
+ if (wallets.length === 0) {
416
+ error('CSV 文件中没有有效的钱包数据');
417
+ process.exit(1);
418
+ }
419
+ // 纯本地存储
420
+ addWalletsToGroup(groupId, wallets);
421
+ output({
422
+ success: true,
423
+ groupId,
424
+ imported: wallets.length,
425
+ addresses: wallets.map(w => w.walletAddress),
426
+ });
427
+ }
428
+ catch (e) {
429
+ error('导入钱包失败', e.message);
430
+ process.exit(1);
431
+ }
432
+ });
433
+ // ============================================================
434
+ // forgex wallet export
435
+ // ============================================================
436
+ walletCmd
437
+ .command('export')
438
+ .description('导出钱包组为 CSV 文件')
439
+ .requiredOption('--group <groupId>', '钱包组 ID')
440
+ .requiredOption('--file <csvPath>', '输出文件路径')
441
+ .action(async (options) => {
442
+ try {
443
+ await ensurePasswordAndValidate();
444
+ const groupId = Number(options.group);
445
+ if (isNaN(groupId) || !Number.isInteger(groupId) || groupId <= 0) {
446
+ error(`无效的钱包组 ID "${options.group}",请提供正整数`);
447
+ process.exit(1);
448
+ }
449
+ const csv = exportGroupToCsv(groupId);
450
+ if (!csv) {
451
+ error(`钱包组 ${groupId} 不存在`);
452
+ process.exit(1);
453
+ }
454
+ fs.writeFileSync(options.file, csv, { encoding: 'utf-8', mode: 0o600 });
455
+ output({
456
+ success: true,
457
+ groupId,
458
+ file: options.file,
459
+ });
460
+ }
461
+ catch (e) {
462
+ error('导出钱包失败', e.message);
463
+ process.exit(1);
464
+ }
465
+ });
466
+ // ============================================================
467
+ // forgex wallet import-group
468
+ // ============================================================
469
+ walletCmd
470
+ .command('import-group')
471
+ .description('从 JSON 文件导入钱包组')
472
+ .requiredOption('--file <jsonPath>', 'JSON 文件路径')
473
+ .option('--password <pwd>', '解密密码(加密文件需要)')
474
+ .action(async (options) => {
475
+ try {
476
+ await ensurePasswordAndValidate();
477
+ // 验证文件存在
478
+ if (!fs.existsSync(options.file)) {
479
+ error(`JSON 文件不存在: ${options.file}`);
480
+ process.exit(1);
481
+ }
482
+ const content = fs.readFileSync(options.file, 'utf-8');
483
+ const groups = importGroupsFromJson(content, options.password);
484
+ if (!groups || groups.length === 0) {
485
+ error('JSON 文件中没有有效的钱包组数据(如果是加密文件,请检查 --password 是否正确)');
486
+ process.exit(1);
487
+ }
488
+ // 纯本地存储(通过 saveGroup + addWalletsToGroup 确保私钥正确加密)
489
+ const existingGroups = getAllGroups();
490
+ let nextLocalId = existingGroups.reduce((max, g) => Math.max(max, g.groupId), 0) + 1;
491
+ groups.forEach((g) => {
492
+ const newId = nextLocalId++;
493
+ const wallets = g.wallets;
494
+ g.groupId = newId;
495
+ g.wallets = []; // 先保存空钱包组
496
+ saveGroup(g);
497
+ // 通过 addWalletsToGroup 添加钱包,该函数会自动处理加密
498
+ if (wallets.length > 0) {
499
+ addWalletsToGroup(newId, wallets);
500
+ }
501
+ });
502
+ output({
503
+ success: true,
504
+ imported: groups.length,
505
+ groupIds: groups.map(g => g.groupId),
506
+ });
507
+ }
508
+ catch (e) {
509
+ error('导入钱包组失败', e.message);
510
+ process.exit(1);
511
+ }
512
+ });
513
+ // ============================================================
514
+ // forgex wallet export-group
515
+ // ============================================================
516
+ walletCmd
517
+ .command('export-group')
518
+ .description('导出所有钱包组为 JSON 文件')
519
+ .requiredOption('--file <jsonPath>', '输出文件路径')
520
+ .option('--encrypt', '加密导出', false)
521
+ .option('--password <pwd>', '加密密码')
522
+ .action(async (options) => {
523
+ try {
524
+ await ensurePasswordAndValidate();
525
+ const groups = getAllGroups();
526
+ if (groups.length === 0) {
527
+ warn('没有钱包组可导出');
528
+ output({ success: true, file: options.file, encrypted: options.encrypt, groupCount: 0 });
529
+ return;
530
+ }
531
+ let content;
532
+ if (options.encrypt) {
533
+ const password = options.password || process.env.FORGEX_WALLET_PASSWORD;
534
+ if (!password) {
535
+ error('加密导出需要密码,使用 --password 或设置 FORGEX_WALLET_PASSWORD 环境变量');
536
+ process.exit(1);
537
+ }
538
+ content = exportEncryptedGroupsJson(password);
539
+ }
540
+ else {
541
+ content = exportAllGroupsJson();
542
+ }
543
+ fs.writeFileSync(options.file, content, { encoding: 'utf-8', mode: 0o600 });
544
+ output({
545
+ success: true,
546
+ file: options.file,
547
+ encrypted: options.encrypt,
548
+ groupCount: groups.length,
549
+ });
550
+ }
551
+ catch (e) {
552
+ error('导出钱包组失败', e.message);
553
+ process.exit(1);
554
+ }
555
+ });
556
+ // ============================================================
557
+ // forgex wallet overview
558
+ // ============================================================
559
+ walletCmd
560
+ .command('overview')
561
+ .description('查看钱包组概览')
562
+ .requiredOption('--groups <ids>', '钱包组 ID(逗号分隔)')
563
+ .option('--token <ca>', '按代币过滤')
564
+ .action(async (options) => {
565
+ try {
566
+ const groupIds = options.groups.split(',').map(Number);
567
+ // 验证所有 groupId 都是有效正整数
568
+ if (groupIds.some(id => isNaN(id) || !Number.isInteger(id) || id <= 0)) {
569
+ error(`无效的钱包组 ID 列表 "${options.groups}",请提供逗号分隔的正整数`);
570
+ process.exit(1);
571
+ }
572
+ // 验证本地是否存在这些钱包组
573
+ const missingIds = groupIds.filter(id => !getGroup(id));
574
+ if (missingIds.length > 0) {
575
+ warn(`以下钱包组在本地不存在: ${missingIds.join(', ')}`);
576
+ }
577
+ const ds = getDataSource();
578
+ // 从 DataStore 本地数据构建概览
579
+ const overviewResults = [];
580
+ for (const gid of groupIds) {
581
+ const group = getGroup(gid);
582
+ if (!group)
583
+ continue;
584
+ // 确定要查询的代币列表
585
+ const tokenCAs = options.token ? [options.token] : ds.listTokens();
586
+ let totalValueSol = 0;
587
+ let totalRealizedPnl = 0;
588
+ let totalUnrealizedPnl = 0;
589
+ let totalCostSol = 0;
590
+ let totalTokenPositions = 0;
591
+ for (const ca of tokenCAs) {
592
+ const holdings = ds.getHoldings(ca, gid);
593
+ if (!holdings || holdings.wallets.length === 0)
594
+ continue;
595
+ // 尝试获取代币价格
596
+ let priceSol = 0;
597
+ try {
598
+ const priceData = await ds.getTokenPrice(ca);
599
+ priceSol = priceData.priceSol;
600
+ }
601
+ catch {
602
+ // 价格不可用,跳过价值计算
603
+ }
604
+ for (const w of holdings.wallets) {
605
+ const positionValue = w.tokenBalance * priceSol;
606
+ totalValueSol += positionValue;
607
+ totalRealizedPnl += w.realizedPnl;
608
+ totalUnrealizedPnl += positionValue - w.tokenBalance * w.avgBuyPrice;
609
+ totalCostSol += w.totalCostSol;
610
+ if (w.tokenBalance > 0)
611
+ totalTokenPositions++;
612
+ }
613
+ }
614
+ const avgCost = totalTokenPositions > 0 ? totalCostSol / totalTokenPositions : 0;
615
+ overviewResults.push({
616
+ groupId: gid,
617
+ groupName: group.name,
618
+ totalValue: totalValueSol.toFixed(4),
619
+ pnl: (totalRealizedPnl + totalUnrealizedPnl).toFixed(4),
620
+ donePnl: totalRealizedPnl.toFixed(4),
621
+ unDonePnl: totalUnrealizedPnl.toFixed(4),
622
+ avgCost: avgCost.toFixed(6),
623
+ });
624
+ }
625
+ if (overviewResults.length === 0) {
626
+ warn('暂无钱包组概览数据');
627
+ }
628
+ output(overviewResults, {
629
+ columns: [
630
+ { key: 'groupId', header: 'ID' },
631
+ { key: 'groupName', header: '名称' },
632
+ { key: 'totalValue', header: '总价值(SOL)' },
633
+ { key: 'pnl', header: '总盈亏(SOL)' },
634
+ { key: 'donePnl', header: '已实现' },
635
+ { key: 'unDonePnl', header: '未实现' },
636
+ { key: 'avgCost', header: '均价' },
637
+ ],
638
+ });
639
+ }
640
+ catch (e) {
641
+ error('获取钱包组概览失败', e.message);
642
+ process.exit(1);
643
+ }
644
+ });
645
+ // ============================================================
646
+ // forgex wallet grind
647
+ // ============================================================
648
+ walletCmd
649
+ .command('grind')
650
+ .description('生成靓号地址(自定义后缀/前缀)')
651
+ .requiredOption('--suffix <suffix>', '地址后缀(如 pump)')
652
+ .option('--prefix <prefix>', '地址前缀')
653
+ .option('--count <n>', '生成数量', '1')
654
+ .option('--threads <n>', 'grind 线程数')
655
+ .action(async (options) => {
656
+ try {
657
+ // 检查 solana-keygen 是否可用
658
+ try {
659
+ execSync('solana-keygen --version', { stdio: 'ignore' });
660
+ }
661
+ catch {
662
+ error('未检测到 solana-keygen,请先安装 Solana CLI 工具链', '安装方式: sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" 或访问 https://docs.solanalabs.com/cli/install');
663
+ process.exit(1);
664
+ }
665
+ const count = Number(options.count);
666
+ if (isNaN(count) || !Number.isInteger(count) || count <= 0) {
667
+ error(`无效的生成数量 "${options.count}",请提供正整数`);
668
+ process.exit(1);
669
+ }
670
+ const suffix = options.suffix.trim();
671
+ if (!suffix) {
672
+ error('--suffix 不能为空');
673
+ process.exit(1);
674
+ }
675
+ // 验证后缀只包含 Base58 字符
676
+ const base58Chars = /^[1-9A-HJ-NP-Za-km-z]+$/;
677
+ if (!base58Chars.test(suffix)) {
678
+ error(`无效的后缀 "${suffix}",只支持 Base58 字符(不含 0, O, I, l)`);
679
+ process.exit(1);
680
+ }
681
+ if (options.prefix && !base58Chars.test(options.prefix)) {
682
+ error(`无效的前缀 "${options.prefix}",只支持 Base58 字符(不含 0, O, I, l)`);
683
+ process.exit(1);
684
+ }
685
+ ensureConfigDir();
686
+ // 构建 solana-keygen grind 参数
687
+ const args = ['grind'];
688
+ if (options.prefix) {
689
+ args.push('--starts-and-ends-with', `${options.prefix}:${suffix}`);
690
+ }
691
+ else {
692
+ args.push('--ends-with', `${suffix}:${count}`);
693
+ }
694
+ if (options.threads) {
695
+ const threads = Number(options.threads);
696
+ if (isNaN(threads) || !Number.isInteger(threads) || threads <= 0) {
697
+ error(`无效的线程数 "${options.threads}",请提供正整数`);
698
+ process.exit(1);
699
+ }
700
+ args.push('--num-threads', String(threads));
701
+ }
702
+ const fmt = getOutputFormat();
703
+ if (fmt !== 'json') {
704
+ success(`正在生成 ${count} 个以 "${suffix}" 结尾的靓号地址,请耐心等待...`);
705
+ if (suffix.length >= 5) {
706
+ warn('后缀较长,生成时间可能需要数分钟甚至更久');
707
+ }
708
+ }
709
+ // 在 VANITY_DIR 下执行 grind,生成的 .json 文件会落在该目录
710
+ const child = spawn('solana-keygen', args, {
711
+ cwd: VANITY_DIR,
712
+ stdio: ['ignore', 'pipe', 'pipe'],
713
+ });
714
+ let stdout = '';
715
+ let stderr = '';
716
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
717
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
718
+ const exitCode = await new Promise((resolve) => {
719
+ child.on('close', resolve);
720
+ });
721
+ if (exitCode !== 0) {
722
+ error('solana-keygen grind 执行失败', stderr || stdout);
723
+ process.exit(1);
724
+ }
725
+ // 解析生成结果,solana-keygen 输出格式: "Wrote keypair to <address>.json"
726
+ const generatedFiles = [];
727
+ const lines = stdout.split('\n');
728
+ for (const line of lines) {
729
+ const match = line.match(/Wrote keypair to (.+\.json)/);
730
+ if (match) {
731
+ const filename = match[1].trim();
732
+ const filePath = path.resolve(VANITY_DIR, filename);
733
+ const address = path.basename(filename, '.json');
734
+ generatedFiles.push({ address, path: filePath });
735
+ }
736
+ }
737
+ // --starts-and-ends-with 模式下需要手动控制 count
738
+ if (options.prefix && generatedFiles.length > count) {
739
+ generatedFiles.splice(count);
740
+ }
741
+ if (generatedFiles.length === 0) {
742
+ warn('未能解析到生成的密钥文件');
743
+ process.exit(1);
744
+ }
745
+ if (fmt === 'json') {
746
+ output({
747
+ success: true,
748
+ count: generatedFiles.length,
749
+ suffix,
750
+ prefix: options.prefix || null,
751
+ dir: VANITY_DIR,
752
+ keypairs: generatedFiles,
753
+ });
754
+ }
755
+ else {
756
+ success(`成功生成 ${generatedFiles.length} 个靓号地址,已保存到 ${VANITY_DIR}`);
757
+ output(generatedFiles.map((f, i) => ({
758
+ index: i + 1,
759
+ address: f.address,
760
+ path: f.path,
761
+ })), {
762
+ columns: [
763
+ { key: 'index', header: '#' },
764
+ { key: 'address', header: '地址' },
765
+ { key: 'path', header: '文件路径' },
766
+ ],
767
+ });
768
+ }
769
+ }
770
+ catch (e) {
771
+ error('生成靓号地址失败', e.message);
772
+ process.exit(1);
773
+ }
774
+ });
775
+ // ============================================================
776
+ // forgex wallet grind-list
777
+ // ============================================================
778
+ walletCmd
779
+ .command('grind-list')
780
+ .description('列出已生成的靓号地址')
781
+ .option('--suffix <suffix>', '按后缀过滤')
782
+ .action((options) => {
783
+ try {
784
+ ensureConfigDir();
785
+ if (!fs.existsSync(VANITY_DIR)) {
786
+ warn('暂无靓号地址,请先运行 forgex wallet grind 生成');
787
+ output([]);
788
+ return;
789
+ }
790
+ const files = fs.readdirSync(VANITY_DIR).filter(f => f.endsWith('.json'));
791
+ if (files.length === 0) {
792
+ warn('暂无靓号地址,请先运行 forgex wallet grind 生成');
793
+ output([]);
794
+ return;
795
+ }
796
+ let results = files.map(f => {
797
+ const address = path.basename(f, '.json');
798
+ const filePath = path.join(VANITY_DIR, f);
799
+ const stat = fs.statSync(filePath);
800
+ return {
801
+ address,
802
+ path: filePath,
803
+ createdAt: stat.birthtime.toISOString().replace('T', ' ').slice(0, 19),
804
+ };
805
+ });
806
+ if (options.suffix) {
807
+ const suffixLower = options.suffix.toLowerCase();
808
+ results = results.filter(r => r.address.toLowerCase().endsWith(suffixLower));
809
+ }
810
+ if (results.length === 0) {
811
+ const filterMsg = options.suffix ? `(后缀: ${options.suffix})` : '';
812
+ warn(`没有找到匹配的靓号地址${filterMsg}`);
813
+ }
814
+ output(results, {
815
+ columns: [
816
+ { key: 'address', header: '地址' },
817
+ { key: 'createdAt', header: '创建时间' },
818
+ { key: 'path', header: '文件路径' },
819
+ ],
820
+ });
821
+ }
822
+ catch (e) {
823
+ error('获取靓号地址列表失败', e.message);
824
+ process.exit(1);
825
+ }
826
+ });
827
+ }
828
+ //# sourceMappingURL=index.js.map