@echoclaw/echo-0g 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 (371) hide show
  1. package/README.md +1175 -0
  2. package/dist/0g-compute/account.d.ts +36 -0
  3. package/dist/0g-compute/account.d.ts.map +1 -0
  4. package/dist/0g-compute/account.js +85 -0
  5. package/dist/0g-compute/account.js.map +1 -0
  6. package/dist/0g-compute/bridge.d.ts +16 -0
  7. package/dist/0g-compute/bridge.d.ts.map +1 -0
  8. package/dist/0g-compute/bridge.js +40 -0
  9. package/dist/0g-compute/bridge.js.map +1 -0
  10. package/dist/0g-compute/broker-factory.d.ts +19 -0
  11. package/dist/0g-compute/broker-factory.d.ts.map +1 -0
  12. package/dist/0g-compute/broker-factory.js +65 -0
  13. package/dist/0g-compute/broker-factory.js.map +1 -0
  14. package/dist/0g-compute/constants.d.ts +10 -0
  15. package/dist/0g-compute/constants.d.ts.map +1 -0
  16. package/dist/0g-compute/constants.js +12 -0
  17. package/dist/0g-compute/constants.js.map +1 -0
  18. package/dist/0g-compute/monitor.d.ts +43 -0
  19. package/dist/0g-compute/monitor.d.ts.map +1 -0
  20. package/dist/0g-compute/monitor.js +302 -0
  21. package/dist/0g-compute/monitor.js.map +1 -0
  22. package/dist/0g-compute/pricing.d.ts +43 -0
  23. package/dist/0g-compute/pricing.d.ts.map +1 -0
  24. package/dist/0g-compute/pricing.js +53 -0
  25. package/dist/0g-compute/pricing.js.map +1 -0
  26. package/dist/0g-compute/sdk-bridge.cjs +17 -0
  27. package/dist/0g-compute/sdk-bridge.cjs.map +1 -0
  28. package/dist/0g-compute/sdk-bridge.d.cts +9 -0
  29. package/dist/0g-compute/sdk-bridge.d.cts.map +1 -0
  30. package/dist/0g-compute/smoke-test.d.ts +11 -0
  31. package/dist/0g-compute/smoke-test.d.ts.map +1 -0
  32. package/dist/0g-compute/smoke-test.js +172 -0
  33. package/dist/0g-compute/smoke-test.js.map +1 -0
  34. package/dist/bot/daemon.d.ts +34 -0
  35. package/dist/bot/daemon.d.ts.map +1 -0
  36. package/dist/bot/daemon.js +386 -0
  37. package/dist/bot/daemon.js.map +1 -0
  38. package/dist/bot/executor.d.ts +14238 -0
  39. package/dist/bot/executor.d.ts.map +1 -0
  40. package/dist/bot/executor.js +183 -0
  41. package/dist/bot/executor.js.map +1 -0
  42. package/dist/bot/nonce-queue.d.ts +20 -0
  43. package/dist/bot/nonce-queue.d.ts.map +1 -0
  44. package/dist/bot/nonce-queue.js +41 -0
  45. package/dist/bot/nonce-queue.js.map +1 -0
  46. package/dist/bot/notify.d.ts +15 -0
  47. package/dist/bot/notify.d.ts.map +1 -0
  48. package/dist/bot/notify.js +98 -0
  49. package/dist/bot/notify.js.map +1 -0
  50. package/dist/bot/orders.d.ts +30 -0
  51. package/dist/bot/orders.d.ts.map +1 -0
  52. package/dist/bot/orders.js +172 -0
  53. package/dist/bot/orders.js.map +1 -0
  54. package/dist/bot/state.d.ts +14 -0
  55. package/dist/bot/state.d.ts.map +1 -0
  56. package/dist/bot/state.js +109 -0
  57. package/dist/bot/state.js.map +1 -0
  58. package/dist/bot/stream.d.ts +28 -0
  59. package/dist/bot/stream.d.ts.map +1 -0
  60. package/dist/bot/stream.js +96 -0
  61. package/dist/bot/stream.js.map +1 -0
  62. package/dist/bot/triggers.d.ts +17 -0
  63. package/dist/bot/triggers.d.ts.map +1 -0
  64. package/dist/bot/triggers.js +95 -0
  65. package/dist/bot/triggers.js.map +1 -0
  66. package/dist/bot/types.d.ts +199 -0
  67. package/dist/bot/types.d.ts.map +1 -0
  68. package/dist/bot/types.js +12 -0
  69. package/dist/bot/types.js.map +1 -0
  70. package/dist/chainscan/client.d.ts +28 -0
  71. package/dist/chainscan/client.d.ts.map +1 -0
  72. package/dist/chainscan/client.js +361 -0
  73. package/dist/chainscan/client.js.map +1 -0
  74. package/dist/chainscan/constants.d.ts +15 -0
  75. package/dist/chainscan/constants.d.ts.map +1 -0
  76. package/dist/chainscan/constants.js +15 -0
  77. package/dist/chainscan/constants.js.map +1 -0
  78. package/dist/chainscan/types.d.ts +148 -0
  79. package/dist/chainscan/types.d.ts.map +1 -0
  80. package/dist/chainscan/types.js +2 -0
  81. package/dist/chainscan/types.js.map +1 -0
  82. package/dist/chainscan/validation.d.ts +35 -0
  83. package/dist/chainscan/validation.d.ts.map +1 -0
  84. package/dist/chainscan/validation.js +97 -0
  85. package/dist/chainscan/validation.js.map +1 -0
  86. package/dist/cli.d.ts +7 -0
  87. package/dist/cli.d.ts.map +1 -0
  88. package/dist/cli.js +328 -0
  89. package/dist/cli.js.map +1 -0
  90. package/dist/commands/0g-compute.d.ts +21 -0
  91. package/dist/commands/0g-compute.d.ts.map +1 -0
  92. package/dist/commands/0g-compute.js +850 -0
  93. package/dist/commands/0g-compute.js.map +1 -0
  94. package/dist/commands/chainscan.d.ts +17 -0
  95. package/dist/commands/chainscan.d.ts.map +1 -0
  96. package/dist/commands/chainscan.js +605 -0
  97. package/dist/commands/chainscan.js.map +1 -0
  98. package/dist/commands/config.d.ts +3 -0
  99. package/dist/commands/config.d.ts.map +1 -0
  100. package/dist/commands/config.js +251 -0
  101. package/dist/commands/config.js.map +1 -0
  102. package/dist/commands/echobook.d.ts +17 -0
  103. package/dist/commands/echobook.d.ts.map +1 -0
  104. package/dist/commands/echobook.js +905 -0
  105. package/dist/commands/echobook.js.map +1 -0
  106. package/dist/commands/jaine-subgraph.d.ts +3 -0
  107. package/dist/commands/jaine-subgraph.d.ts.map +1 -0
  108. package/dist/commands/jaine-subgraph.js +565 -0
  109. package/dist/commands/jaine-subgraph.js.map +1 -0
  110. package/dist/commands/jaine.d.ts +3 -0
  111. package/dist/commands/jaine.d.ts.map +1 -0
  112. package/dist/commands/jaine.js +1415 -0
  113. package/dist/commands/jaine.js.map +1 -0
  114. package/dist/commands/marketmaker.d.ts +6 -0
  115. package/dist/commands/marketmaker.d.ts.map +1 -0
  116. package/dist/commands/marketmaker.js +451 -0
  117. package/dist/commands/marketmaker.js.map +1 -0
  118. package/dist/commands/send.d.ts +3 -0
  119. package/dist/commands/send.d.ts.map +1 -0
  120. package/dist/commands/send.js +229 -0
  121. package/dist/commands/send.js.map +1 -0
  122. package/dist/commands/setup.d.ts +3 -0
  123. package/dist/commands/setup.d.ts.map +1 -0
  124. package/dist/commands/setup.js +263 -0
  125. package/dist/commands/setup.js.map +1 -0
  126. package/dist/commands/slop-app.d.ts +9 -0
  127. package/dist/commands/slop-app.d.ts.map +1 -0
  128. package/dist/commands/slop-app.js +708 -0
  129. package/dist/commands/slop-app.js.map +1 -0
  130. package/dist/commands/slop-stream.d.ts +9 -0
  131. package/dist/commands/slop-stream.d.ts.map +1 -0
  132. package/dist/commands/slop-stream.js +99 -0
  133. package/dist/commands/slop-stream.js.map +1 -0
  134. package/dist/commands/slop.d.ts +3 -0
  135. package/dist/commands/slop.d.ts.map +1 -0
  136. package/dist/commands/slop.js +1053 -0
  137. package/dist/commands/slop.js.map +1 -0
  138. package/dist/commands/wallet.d.ts +13 -0
  139. package/dist/commands/wallet.d.ts.map +1 -0
  140. package/dist/commands/wallet.js +748 -0
  141. package/dist/commands/wallet.js.map +1 -0
  142. package/dist/config/paths.d.ts +13 -0
  143. package/dist/config/paths.d.ts.map +1 -0
  144. package/dist/config/paths.js +33 -0
  145. package/dist/config/paths.js.map +1 -0
  146. package/dist/config/store.d.ts +48 -0
  147. package/dist/config/store.d.ts.map +1 -0
  148. package/dist/config/store.js +113 -0
  149. package/dist/config/store.js.map +1 -0
  150. package/dist/constants/chain.d.ts +57 -0
  151. package/dist/constants/chain.d.ts.map +1 -0
  152. package/dist/constants/chain.js +51 -0
  153. package/dist/constants/chain.js.map +1 -0
  154. package/dist/echobook/api.d.ts +38 -0
  155. package/dist/echobook/api.d.ts.map +1 -0
  156. package/dist/echobook/api.js +86 -0
  157. package/dist/echobook/api.js.map +1 -0
  158. package/dist/echobook/auth.d.ts +31 -0
  159. package/dist/echobook/auth.d.ts.map +1 -0
  160. package/dist/echobook/auth.js +93 -0
  161. package/dist/echobook/auth.js.map +1 -0
  162. package/dist/echobook/comments.d.ts +26 -0
  163. package/dist/echobook/comments.d.ts.map +1 -0
  164. package/dist/echobook/comments.js +20 -0
  165. package/dist/echobook/comments.js.map +1 -0
  166. package/dist/echobook/follows.d.ts +19 -0
  167. package/dist/echobook/follows.d.ts.map +1 -0
  168. package/dist/echobook/follows.js +21 -0
  169. package/dist/echobook/follows.js.map +1 -0
  170. package/dist/echobook/jwtCache.d.ts +15 -0
  171. package/dist/echobook/jwtCache.d.ts.map +1 -0
  172. package/dist/echobook/jwtCache.js +63 -0
  173. package/dist/echobook/jwtCache.js.map +1 -0
  174. package/dist/echobook/notifications.d.ts +30 -0
  175. package/dist/echobook/notifications.d.ts.map +1 -0
  176. package/dist/echobook/notifications.js +26 -0
  177. package/dist/echobook/notifications.js.map +1 -0
  178. package/dist/echobook/points.d.ts +35 -0
  179. package/dist/echobook/points.d.ts.map +1 -0
  180. package/dist/echobook/points.js +20 -0
  181. package/dist/echobook/points.js.map +1 -0
  182. package/dist/echobook/posts.d.ts +46 -0
  183. package/dist/echobook/posts.d.ts.map +1 -0
  184. package/dist/echobook/posts.js +43 -0
  185. package/dist/echobook/posts.js.map +1 -0
  186. package/dist/echobook/profile.d.ts +29 -0
  187. package/dist/echobook/profile.d.ts.map +1 -0
  188. package/dist/echobook/profile.js +14 -0
  189. package/dist/echobook/profile.js.map +1 -0
  190. package/dist/echobook/submolts.d.ts +22 -0
  191. package/dist/echobook/submolts.d.ts.map +1 -0
  192. package/dist/echobook/submolts.js +24 -0
  193. package/dist/echobook/submolts.js.map +1 -0
  194. package/dist/echobook/tradeProof.d.ts +21 -0
  195. package/dist/echobook/tradeProof.d.ts.map +1 -0
  196. package/dist/echobook/tradeProof.js +14 -0
  197. package/dist/echobook/tradeProof.js.map +1 -0
  198. package/dist/echobook/votes.d.ts +17 -0
  199. package/dist/echobook/votes.d.ts.map +1 -0
  200. package/dist/echobook/votes.js +20 -0
  201. package/dist/echobook/votes.js.map +1 -0
  202. package/dist/errors.d.ts +125 -0
  203. package/dist/errors.d.ts.map +1 -0
  204. package/dist/errors.js +147 -0
  205. package/dist/errors.js.map +1 -0
  206. package/dist/intents/store.d.ts +22 -0
  207. package/dist/intents/store.d.ts.map +1 -0
  208. package/dist/intents/store.js +76 -0
  209. package/dist/intents/store.js.map +1 -0
  210. package/dist/intents/types.d.ts +21 -0
  211. package/dist/intents/types.d.ts.map +1 -0
  212. package/dist/intents/types.js +2 -0
  213. package/dist/intents/types.js.map +1 -0
  214. package/dist/jaine/abi/erc20.d.ts +90 -0
  215. package/dist/jaine/abi/erc20.d.ts.map +1 -0
  216. package/dist/jaine/abi/erc20.js +65 -0
  217. package/dist/jaine/abi/erc20.js.map +1 -0
  218. package/dist/jaine/abi/factory.d.ts +38 -0
  219. package/dist/jaine/abi/factory.d.ts.map +1 -0
  220. package/dist/jaine/abi/factory.js +26 -0
  221. package/dist/jaine/abi/factory.js.map +1 -0
  222. package/dist/jaine/abi/index.d.ts +11 -0
  223. package/dist/jaine/abi/index.d.ts.map +1 -0
  224. package/dist/jaine/abi/index.js +11 -0
  225. package/dist/jaine/abi/index.js.map +1 -0
  226. package/dist/jaine/abi/nftManager.d.ts +282 -0
  227. package/dist/jaine/abi/nftManager.d.ts.map +1 -0
  228. package/dist/jaine/abi/nftManager.js +182 -0
  229. package/dist/jaine/abi/nftManager.js.map +1 -0
  230. package/dist/jaine/abi/pool.d.ts +77 -0
  231. package/dist/jaine/abi/pool.d.ts.map +1 -0
  232. package/dist/jaine/abi/pool.js +56 -0
  233. package/dist/jaine/abi/pool.js.map +1 -0
  234. package/dist/jaine/abi/quoter.d.ts +84 -0
  235. package/dist/jaine/abi/quoter.d.ts.map +1 -0
  236. package/dist/jaine/abi/quoter.js +53 -0
  237. package/dist/jaine/abi/quoter.js.map +1 -0
  238. package/dist/jaine/abi/router.d.ts +135 -0
  239. package/dist/jaine/abi/router.d.ts.map +1 -0
  240. package/dist/jaine/abi/router.js +88 -0
  241. package/dist/jaine/abi/router.js.map +1 -0
  242. package/dist/jaine/abi/w0g.d.ts +41 -0
  243. package/dist/jaine/abi/w0g.d.ts.map +1 -0
  244. package/dist/jaine/abi/w0g.js +34 -0
  245. package/dist/jaine/abi/w0g.js.map +1 -0
  246. package/dist/jaine/allowance.d.ts +48 -0
  247. package/dist/jaine/allowance.d.ts.map +1 -0
  248. package/dist/jaine/allowance.js +192 -0
  249. package/dist/jaine/allowance.js.map +1 -0
  250. package/dist/jaine/coreTokens.d.ts +32 -0
  251. package/dist/jaine/coreTokens.d.ts.map +1 -0
  252. package/dist/jaine/coreTokens.js +91 -0
  253. package/dist/jaine/coreTokens.js.map +1 -0
  254. package/dist/jaine/pathEncoding.d.ts +39 -0
  255. package/dist/jaine/pathEncoding.d.ts.map +1 -0
  256. package/dist/jaine/pathEncoding.js +98 -0
  257. package/dist/jaine/pathEncoding.js.map +1 -0
  258. package/dist/jaine/paths.d.ts +11 -0
  259. package/dist/jaine/paths.d.ts.map +1 -0
  260. package/dist/jaine/paths.js +20 -0
  261. package/dist/jaine/paths.js.map +1 -0
  262. package/dist/jaine/poolCache.d.ts +47 -0
  263. package/dist/jaine/poolCache.d.ts.map +1 -0
  264. package/dist/jaine/poolCache.js +195 -0
  265. package/dist/jaine/poolCache.js.map +1 -0
  266. package/dist/jaine/routing.d.ts +41 -0
  267. package/dist/jaine/routing.d.ts.map +1 -0
  268. package/dist/jaine/routing.js +247 -0
  269. package/dist/jaine/routing.js.map +1 -0
  270. package/dist/jaine/subgraph/client.d.ts +26 -0
  271. package/dist/jaine/subgraph/client.d.ts.map +1 -0
  272. package/dist/jaine/subgraph/client.js +201 -0
  273. package/dist/jaine/subgraph/client.js.map +1 -0
  274. package/dist/jaine/subgraph/constants.d.ts +9 -0
  275. package/dist/jaine/subgraph/constants.d.ts.map +1 -0
  276. package/dist/jaine/subgraph/constants.js +9 -0
  277. package/dist/jaine/subgraph/constants.js.map +1 -0
  278. package/dist/jaine/subgraph/queries.d.ts +21 -0
  279. package/dist/jaine/subgraph/queries.d.ts.map +1 -0
  280. package/dist/jaine/subgraph/queries.js +304 -0
  281. package/dist/jaine/subgraph/queries.js.map +1 -0
  282. package/dist/jaine/subgraph/types.d.ts +209 -0
  283. package/dist/jaine/subgraph/types.d.ts.map +1 -0
  284. package/dist/jaine/subgraph/types.js +7 -0
  285. package/dist/jaine/subgraph/types.js.map +1 -0
  286. package/dist/jaine/userTokens.d.ts +27 -0
  287. package/dist/jaine/userTokens.d.ts.map +1 -0
  288. package/dist/jaine/userTokens.js +89 -0
  289. package/dist/jaine/userTokens.js.map +1 -0
  290. package/dist/openclaw/config.d.ts +43 -0
  291. package/dist/openclaw/config.d.ts.map +1 -0
  292. package/dist/openclaw/config.js +231 -0
  293. package/dist/openclaw/config.js.map +1 -0
  294. package/dist/openclaw/hooks-client.d.ts +24 -0
  295. package/dist/openclaw/hooks-client.d.ts.map +1 -0
  296. package/dist/openclaw/hooks-client.js +119 -0
  297. package/dist/openclaw/hooks-client.js.map +1 -0
  298. package/dist/slop/abi/factory.d.ts +128 -0
  299. package/dist/slop/abi/factory.d.ts.map +1 -0
  300. package/dist/slop/abi/factory.js +70 -0
  301. package/dist/slop/abi/factory.js.map +1 -0
  302. package/dist/slop/abi/feeCollector.d.ts +95 -0
  303. package/dist/slop/abi/feeCollector.d.ts.map +1 -0
  304. package/dist/slop/abi/feeCollector.js +71 -0
  305. package/dist/slop/abi/feeCollector.js.map +1 -0
  306. package/dist/slop/abi/index.d.ts +5 -0
  307. package/dist/slop/abi/index.d.ts.map +1 -0
  308. package/dist/slop/abi/index.js +5 -0
  309. package/dist/slop/abi/index.js.map +1 -0
  310. package/dist/slop/abi/registry.d.ts +135 -0
  311. package/dist/slop/abi/registry.d.ts.map +1 -0
  312. package/dist/slop/abi/registry.js +90 -0
  313. package/dist/slop/abi/registry.js.map +1 -0
  314. package/dist/slop/abi/token.d.ts +320 -0
  315. package/dist/slop/abi/token.d.ts.map +1 -0
  316. package/dist/slop/abi/token.js +251 -0
  317. package/dist/slop/abi/token.js.map +1 -0
  318. package/dist/slop/auth.d.ts +19 -0
  319. package/dist/slop/auth.d.ts.map +1 -0
  320. package/dist/slop/auth.js +92 -0
  321. package/dist/slop/auth.js.map +1 -0
  322. package/dist/slop/jwtCache.d.ts +27 -0
  323. package/dist/slop/jwtCache.d.ts.map +1 -0
  324. package/dist/slop/jwtCache.js +91 -0
  325. package/dist/slop/jwtCache.js.map +1 -0
  326. package/dist/slop/quote.d.ts +80 -0
  327. package/dist/slop/quote.d.ts.map +1 -0
  328. package/dist/slop/quote.js +174 -0
  329. package/dist/slop/quote.js.map +1 -0
  330. package/dist/utils/canonicalJson.d.ts +8 -0
  331. package/dist/utils/canonicalJson.d.ts.map +1 -0
  332. package/dist/utils/canonicalJson.js +20 -0
  333. package/dist/utils/canonicalJson.js.map +1 -0
  334. package/dist/utils/env.d.ts +11 -0
  335. package/dist/utils/env.d.ts.map +1 -0
  336. package/dist/utils/env.js +20 -0
  337. package/dist/utils/env.js.map +1 -0
  338. package/dist/utils/http.d.ts +19 -0
  339. package/dist/utils/http.d.ts.map +1 -0
  340. package/dist/utils/http.js +61 -0
  341. package/dist/utils/http.js.map +1 -0
  342. package/dist/utils/logger.d.ts +4 -0
  343. package/dist/utils/logger.d.ts.map +1 -0
  344. package/dist/utils/logger.js +21 -0
  345. package/dist/utils/logger.js.map +1 -0
  346. package/dist/utils/output.d.ts +29 -0
  347. package/dist/utils/output.d.ts.map +1 -0
  348. package/dist/utils/output.js +51 -0
  349. package/dist/utils/output.js.map +1 -0
  350. package/dist/utils/rateLimit.d.ts +22 -0
  351. package/dist/utils/rateLimit.d.ts.map +1 -0
  352. package/dist/utils/rateLimit.js +58 -0
  353. package/dist/utils/rateLimit.js.map +1 -0
  354. package/dist/utils/respond.d.ts +19 -0
  355. package/dist/utils/respond.d.ts.map +1 -0
  356. package/dist/utils/respond.js +25 -0
  357. package/dist/utils/respond.js.map +1 -0
  358. package/dist/utils/ui.d.ts +38 -0
  359. package/dist/utils/ui.d.ts.map +1 -0
  360. package/dist/utils/ui.js +126 -0
  361. package/dist/utils/ui.js.map +1 -0
  362. package/dist/wallet/client.d.ts +4 -0
  363. package/dist/wallet/client.d.ts.map +1 -0
  364. package/dist/wallet/client.js +53 -0
  365. package/dist/wallet/client.js.map +1 -0
  366. package/dist/wallet/keystore.d.ts +22 -0
  367. package/dist/wallet/keystore.d.ts.map +1 -0
  368. package/dist/wallet/keystore.js +111 -0
  369. package/dist/wallet/keystore.js.map +1 -0
  370. package/package.json +63 -0
  371. package/skills/echo/SKILL.md +1121 -0
@@ -0,0 +1,748 @@
1
+ import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, cpSync, rmSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { platform } from "node:os";
5
+ import { Command } from "commander";
6
+ import { isAddress, getAddress } from "viem";
7
+ import { generatePrivateKey, privateKeyToAddress } from "viem/accounts";
8
+ import { loadConfig, saveConfig } from "../config/store.js";
9
+ import { CONFIG_DIR, BACKUPS_DIR } from "../config/paths.js";
10
+ import { getPublicClient } from "../wallet/client.js";
11
+ import { encryptPrivateKey, decryptPrivateKey, saveKeystore, loadKeystore, keystoreExists, normalizePrivateKey } from "../wallet/keystore.js";
12
+ import { ERC20_ABI } from "../constants/chain.js";
13
+ import { EchoError, ErrorCodes } from "../errors.js";
14
+ import { requireKeystorePassword, getKeystorePassword } from "../utils/env.js";
15
+ import { successBox, infoBox, warnBox, spinner, printTable, colors, formatAddress, formatBalance, } from "../utils/ui.js";
16
+ import { writeStdout, writeStderr, isHeadless, isStdoutTTY, writeJsonSuccess } from "../utils/output.js";
17
+ import logger from "../utils/logger.js";
18
+ const MAX_WATCHLIST_TOKENS = 200;
19
+ const MAX_BACKUPS = 20;
20
+ function getCLIVersion() {
21
+ try {
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ const pkgPath = join(__dirname, "..", "..", "package.json");
24
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
25
+ return pkg.version ?? "unknown";
26
+ }
27
+ catch {
28
+ return "unknown";
29
+ }
30
+ }
31
+ /**
32
+ * Create a backup of keystore.json and/or config.json.
33
+ * Returns backup path, or null if nothing to back up.
34
+ * Throws EchoError(AUTO_BACKUP_FAILED) on write failure.
35
+ */
36
+ export async function autoBackup() {
37
+ const keystorePath = join(CONFIG_DIR, "keystore.json");
38
+ const configPath = join(CONFIG_DIR, "config.json");
39
+ const hasKeystore = existsSync(keystorePath);
40
+ const hasConfig = existsSync(configPath);
41
+ if (!hasKeystore && !hasConfig) {
42
+ return null;
43
+ }
44
+ try {
45
+ mkdirSync(BACKUPS_DIR, { recursive: true });
46
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "").replace("Z", "Z");
47
+ const backupDir = join(BACKUPS_DIR, timestamp);
48
+ mkdirSync(backupDir, { recursive: true });
49
+ const files = [];
50
+ if (hasKeystore) {
51
+ cpSync(keystorePath, join(backupDir, "keystore.json"));
52
+ files.push("keystore.json");
53
+ }
54
+ if (hasConfig) {
55
+ cpSync(configPath, join(backupDir, "config.json"));
56
+ files.push("config.json");
57
+ }
58
+ const cfg = loadConfig();
59
+ const manifest = {
60
+ version: 1,
61
+ cliVersion: getCLIVersion(),
62
+ createdAt: new Date().toISOString(),
63
+ walletAddress: cfg.wallet.address ?? null,
64
+ chainId: cfg.chain.chainId,
65
+ files,
66
+ };
67
+ writeFileSync(join(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
68
+ // Enforce retention: remove oldest if over MAX_BACKUPS
69
+ enforceBackupRetention();
70
+ logger.debug(`Auto-backup created at ${backupDir}`);
71
+ return backupDir;
72
+ }
73
+ catch (err) {
74
+ throw new EchoError(ErrorCodes.AUTO_BACKUP_FAILED, `Failed to create auto-backup: ${err instanceof Error ? err.message : String(err)}`, "Check permissions on the config directory.");
75
+ }
76
+ }
77
+ function enforceBackupRetention() {
78
+ if (!existsSync(BACKUPS_DIR))
79
+ return;
80
+ try {
81
+ const entries = readdirSync(BACKUPS_DIR, { withFileTypes: true })
82
+ .filter((d) => d.isDirectory())
83
+ .map((d) => d.name)
84
+ .sort();
85
+ while (entries.length > MAX_BACKUPS) {
86
+ const oldest = entries.shift();
87
+ rmSync(join(BACKUPS_DIR, oldest), { recursive: true, force: true });
88
+ logger.debug(`Removed old backup: ${oldest}`);
89
+ }
90
+ }
91
+ catch {
92
+ // best-effort
93
+ }
94
+ }
95
+ function listBackups() {
96
+ if (!existsSync(BACKUPS_DIR))
97
+ return [];
98
+ try {
99
+ return readdirSync(BACKUPS_DIR, { withFileTypes: true })
100
+ .filter((d) => d.isDirectory())
101
+ .map((d) => {
102
+ const manifestPath = join(BACKUPS_DIR, d.name, "manifest.json");
103
+ if (!existsSync(manifestPath))
104
+ return null;
105
+ try {
106
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
107
+ return { dir: join(BACKUPS_DIR, d.name), manifest };
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ })
113
+ .filter((b) => b !== null)
114
+ .sort((a, b) => a.manifest.createdAt.localeCompare(b.manifest.createdAt));
115
+ }
116
+ catch {
117
+ return [];
118
+ }
119
+ }
120
+ // ──────────────────────────────────────────────
121
+ // Import action (shared between `wallet import` and top-level `import`)
122
+ // ──────────────────────────────────────────────
123
+ export async function importPrivateKeyAction(privateKeyArg, options) {
124
+ // 1. Determine private key source: argument > --stdin > ECHO_IMPORT_KEY env
125
+ let rawKey = privateKeyArg;
126
+ if (!rawKey && options.stdin) {
127
+ try {
128
+ rawKey = readFileSync(0, "utf-8").trim();
129
+ }
130
+ catch {
131
+ throw new EchoError(ErrorCodes.INVALID_PRIVATE_KEY, "Failed to read private key from stdin.", "Usage: echo $KEY | echo wallet import --stdin");
132
+ }
133
+ }
134
+ if (!rawKey && process.env.ECHO_IMPORT_KEY) {
135
+ rawKey = process.env.ECHO_IMPORT_KEY;
136
+ }
137
+ if (!rawKey) {
138
+ throw new EchoError(ErrorCodes.INVALID_PRIVATE_KEY, "No private key provided.", "Provide via argument, --stdin, or ECHO_IMPORT_KEY env var.\n" +
139
+ " echo wallet import 0xABC...\n" +
140
+ " echo $KEY | echo wallet import --stdin\n" +
141
+ " ECHO_IMPORT_KEY=0x... echo wallet import");
142
+ }
143
+ // 2. Validate key
144
+ let normalizedKey;
145
+ try {
146
+ normalizedKey = normalizePrivateKey(rawKey);
147
+ }
148
+ catch (err) {
149
+ throw new EchoError(ErrorCodes.INVALID_PRIVATE_KEY, `Invalid private key: ${err instanceof Error ? err.message : String(err)}`, "Private key must be 32 bytes hex (64 characters), optionally prefixed with 0x.");
150
+ }
151
+ // 3. Check existing keystore
152
+ if (keystoreExists() && !options.force) {
153
+ throw new EchoError(ErrorCodes.KEYSTORE_ALREADY_EXISTS, "Keystore already exists.", "Use --force to overwrite. Existing keystore will be backed up automatically.");
154
+ }
155
+ // 4. Auto-backup before overwrite
156
+ if (options.force && keystoreExists()) {
157
+ await autoBackup();
158
+ }
159
+ // 5. Get password
160
+ const password = requireKeystorePassword();
161
+ // 6. Encrypt and save
162
+ const spin = spinner("Encrypting and saving keystore...");
163
+ spin.start();
164
+ const keystore = encryptPrivateKey(normalizedKey, password);
165
+ saveKeystore(keystore);
166
+ // 7. Derive address and update config
167
+ const address = privateKeyToAddress(normalizedKey);
168
+ const cfg = loadConfig();
169
+ cfg.wallet.address = address;
170
+ saveConfig(cfg);
171
+ spin.succeed("Wallet imported");
172
+ // 8. Output (NEVER print private key)
173
+ const result = { address, chainId: cfg.chain.chainId };
174
+ if (isHeadless()) {
175
+ writeJsonSuccess(result);
176
+ }
177
+ else {
178
+ successBox("Wallet Imported", `Address: ${colors.address(address)}\n` +
179
+ `Chain: ${colors.info(cfg.chain.chainId.toString())}\n\n` +
180
+ colors.warn("Private key encrypted and stored locally.\n") +
181
+ colors.muted("Tip: Prefer --stdin or env var over argument (avoids shell history)."));
182
+ }
183
+ }
184
+ function requireWallet() {
185
+ const cfg = loadConfig();
186
+ if (!cfg.wallet.address) {
187
+ throw new EchoError(ErrorCodes.WALLET_NOT_CONFIGURED, "No wallet configured.", "Run: echo wallet ensure --json (to check status and get instructions)");
188
+ }
189
+ return cfg.wallet.address;
190
+ }
191
+ export function createWalletCommand() {
192
+ const wallet = new Command("wallet")
193
+ .description("Wallet operations")
194
+ .exitOverride();
195
+ // echo wallet create
196
+ wallet
197
+ .command("create")
198
+ .description("Generate new wallet and save encrypted keystore")
199
+ .option("--force", "Overwrite existing keystore")
200
+ .action(async (options) => {
201
+ // 1. Check if keystore already exists
202
+ if (keystoreExists() && !options.force) {
203
+ throw new EchoError(ErrorCodes.KEYSTORE_ALREADY_EXISTS, "Keystore already exists.", "Use --force to overwrite. Existing keystore will be backed up automatically.");
204
+ }
205
+ // 2. Auto-backup before overwrite
206
+ if (options.force && keystoreExists()) {
207
+ await autoBackup();
208
+ }
209
+ // 3. Get password from env (required for automation)
210
+ const password = requireKeystorePassword();
211
+ // 4. Generate private key (NEVER print to stdout!)
212
+ const privateKey = generatePrivateKey();
213
+ const address = privateKeyToAddress(privateKey);
214
+ // 4. Encrypt and save
215
+ const spin = spinner("Encrypting and saving keystore...");
216
+ spin.start();
217
+ const keystore = encryptPrivateKey(privateKey, password);
218
+ saveKeystore(keystore);
219
+ // 5. Update config with address
220
+ const cfg = loadConfig();
221
+ cfg.wallet.address = address;
222
+ saveConfig(cfg);
223
+ spin.succeed("Wallet created");
224
+ // 6. Output
225
+ const result = { address, chainId: cfg.chain.chainId };
226
+ if (isHeadless()) {
227
+ writeJsonSuccess(result);
228
+ }
229
+ else {
230
+ successBox("Wallet Created", `Address: ${colors.address(address)}\n` +
231
+ `Chain: ${colors.info(cfg.chain.chainId.toString())}\n\n` +
232
+ colors.warn("⚠ Private key encrypted and stored locally."));
233
+ }
234
+ });
235
+ // echo wallet address
236
+ wallet
237
+ .command("address")
238
+ .description("Display configured wallet address")
239
+ .action(async () => {
240
+ const address = requireWallet();
241
+ if (isHeadless()) {
242
+ writeJsonSuccess({ address });
243
+ }
244
+ else {
245
+ writeStdout(address);
246
+ }
247
+ });
248
+ // echo wallet balance
249
+ wallet
250
+ .command("balance")
251
+ .description("Show native and token balances")
252
+ .option("-t, --tokens", "Include watchlist tokens")
253
+ .action(async (options) => {
254
+ const address = requireWallet();
255
+ const cfg = loadConfig();
256
+ const client = getPublicClient();
257
+ const spin = spinner(`Fetching balance from ${cfg.chain.chainId === 16661 ? "0G Mainnet" : `chain ${cfg.chain.chainId}`}...`);
258
+ spin.start();
259
+ try {
260
+ // Fetch native balance
261
+ const nativeBalance = await client.getBalance({ address });
262
+ spin.succeed("Balance fetched");
263
+ // Prepare table data
264
+ const rows = [];
265
+ // Native balance (0G)
266
+ rows.push([
267
+ colors.bold("0G"),
268
+ colors.value(formatBalance(nativeBalance, 18)),
269
+ colors.muted("native"),
270
+ ]);
271
+ const tokenRows = [];
272
+ if (options.tokens && cfg.watchlist.tokens.length > 0) {
273
+ const tokenSpin = spinner(`Fetching ${cfg.watchlist.tokens.length} token(s)...`);
274
+ tokenSpin.start();
275
+ for (const tokenAddr of cfg.watchlist.tokens) {
276
+ try {
277
+ // Multicall for symbol, decimals, balanceOf
278
+ const [symbol, decimals, balance] = await Promise.all([
279
+ client.readContract({
280
+ address: tokenAddr,
281
+ abi: ERC20_ABI,
282
+ functionName: "symbol",
283
+ }).catch(() => null),
284
+ client.readContract({
285
+ address: tokenAddr,
286
+ abi: ERC20_ABI,
287
+ functionName: "decimals",
288
+ }).catch(() => 18),
289
+ client.readContract({
290
+ address: tokenAddr,
291
+ abi: ERC20_ABI,
292
+ functionName: "balanceOf",
293
+ args: [address],
294
+ }),
295
+ ]);
296
+ const displaySymbol = (symbol ?? formatAddress(tokenAddr, 4));
297
+ const rawDecimals = typeof decimals === "bigint"
298
+ ? Number(decimals)
299
+ : typeof decimals === "number"
300
+ ? decimals
301
+ : 18;
302
+ const displayDecimals = Math.min(255, Math.max(0, rawDecimals));
303
+ // Store for JSON output
304
+ tokenRows.push({
305
+ address: tokenAddr,
306
+ symbol: displaySymbol,
307
+ decimals: displayDecimals,
308
+ balanceWei: balance.toString(),
309
+ balance: formatBalance(balance, displayDecimals),
310
+ });
311
+ rows.push([
312
+ colors.bold(displaySymbol),
313
+ colors.value(formatBalance(balance, displayDecimals)),
314
+ colors.muted(formatAddress(tokenAddr, 4)),
315
+ ]);
316
+ }
317
+ catch (err) {
318
+ logger.debug(`Failed to fetch token ${tokenAddr}: ${err}`);
319
+ tokenRows.push({
320
+ address: tokenAddr,
321
+ symbol: formatAddress(tokenAddr, 4),
322
+ decimals: 18,
323
+ balanceWei: "0",
324
+ balance: "0",
325
+ error: true,
326
+ });
327
+ rows.push([
328
+ colors.muted(formatAddress(tokenAddr, 4)),
329
+ colors.error("error"),
330
+ colors.muted(formatAddress(tokenAddr, 4)),
331
+ ]);
332
+ }
333
+ }
334
+ tokenSpin.succeed(`Fetched ${cfg.watchlist.tokens.length} token(s)`);
335
+ }
336
+ // JSON output for automation
337
+ if (isHeadless()) {
338
+ const result = {
339
+ address,
340
+ chainId: cfg.chain.chainId,
341
+ native: {
342
+ symbol: "0G",
343
+ balanceWei: nativeBalance.toString(),
344
+ balance: formatBalance(nativeBalance, 18),
345
+ },
346
+ };
347
+ // Include tokens if requested and fetched
348
+ if (options.tokens && cfg.watchlist.tokens.length > 0) {
349
+ result.tokens = tokenRows;
350
+ }
351
+ writeJsonSuccess(result);
352
+ return;
353
+ }
354
+ // Print header
355
+ writeStderr("");
356
+ infoBox("Wallet Balance", colors.address(address));
357
+ // Print table
358
+ printTable([
359
+ { header: "Token", width: 12 },
360
+ { header: "Balance", width: 20 },
361
+ { header: "Address", width: 16 },
362
+ ], rows);
363
+ if (!options.tokens && cfg.watchlist.tokens.length > 0) {
364
+ writeStderr(colors.muted(`\nTip: Use ${colors.info("--tokens")} to include ${cfg.watchlist.tokens.length} watchlist token(s)`));
365
+ }
366
+ }
367
+ catch (err) {
368
+ spin.fail("Failed to fetch balance");
369
+ const errMsg = err instanceof Error ? err.message : String(err);
370
+ if (errMsg.includes("fetch") || errMsg.includes("timeout") || errMsg.includes("ECONNREFUSED")) {
371
+ throw new EchoError(ErrorCodes.RPC_ERROR, `Could not connect to RPC: ${cfg.chain.rpcUrl}`, "Check your network or run: echo config set-rpc <new-url>");
372
+ }
373
+ throw new EchoError(ErrorCodes.RPC_ERROR, `RPC error: ${errMsg}`);
374
+ }
375
+ });
376
+ // echo wallet import <privateKey>
377
+ wallet
378
+ .command("import")
379
+ .description("Import private key into encrypted keystore (non-interactive)")
380
+ .argument("[privateKey]", "Private key hex (0x-prefixed or raw)")
381
+ .option("--stdin", "Read private key from stdin")
382
+ .option("--force", "Overwrite existing keystore (auto-backup first)")
383
+ .action(importPrivateKeyAction);
384
+ // echo wallet ensure
385
+ wallet
386
+ .command("ensure")
387
+ .description("Check wallet readiness (idempotent status check)")
388
+ .action(async () => {
389
+ const cfg = loadConfig();
390
+ const address = cfg.wallet.address;
391
+ const hasKeystore = keystoreExists();
392
+ const passwordSet = getKeystorePassword() !== null;
393
+ // Missing keystore
394
+ if (!hasKeystore) {
395
+ const result = {
396
+ status: "missing_keystore",
397
+ address: null,
398
+ hasKeystore: false,
399
+ passwordSet,
400
+ hint: "Run: echo wallet create --json OR echo wallet import <key>",
401
+ };
402
+ if (isHeadless()) {
403
+ writeJsonSuccess(result);
404
+ }
405
+ else {
406
+ warnBox("Missing Keystore", result.hint);
407
+ }
408
+ return;
409
+ }
410
+ // Missing password
411
+ if (!passwordSet) {
412
+ const result = {
413
+ status: "missing_password",
414
+ address: address ?? null,
415
+ hasKeystore: true,
416
+ passwordSet: false,
417
+ hint: "Run: echo setup password --from-env",
418
+ };
419
+ if (isHeadless()) {
420
+ writeJsonSuccess(result);
421
+ }
422
+ else {
423
+ warnBox("Missing Password", result.hint);
424
+ }
425
+ return;
426
+ }
427
+ // Try decrypt to verify password matches
428
+ const keystore = loadKeystore();
429
+ if (!keystore) {
430
+ const result = {
431
+ status: "missing_keystore",
432
+ address: null,
433
+ hasKeystore: false,
434
+ passwordSet,
435
+ hint: "Keystore file exists but could not be read. Run: echo wallet create --force --json",
436
+ };
437
+ if (isHeadless()) {
438
+ writeJsonSuccess(result);
439
+ }
440
+ else {
441
+ warnBox("Corrupt Keystore", result.hint);
442
+ }
443
+ return;
444
+ }
445
+ let derivedAddress;
446
+ try {
447
+ const pk = decryptPrivateKey(keystore, getKeystorePassword());
448
+ derivedAddress = privateKeyToAddress(pk);
449
+ }
450
+ catch {
451
+ const result = {
452
+ status: "password_mismatch",
453
+ address: address ?? null,
454
+ hasKeystore: true,
455
+ passwordSet: true,
456
+ hint: "Password does not decrypt keystore. Check ECHO_KEYSTORE_PASSWORD.",
457
+ };
458
+ if (isHeadless()) {
459
+ writeJsonSuccess(result);
460
+ }
461
+ else {
462
+ warnBox("Password Mismatch", result.hint);
463
+ }
464
+ return;
465
+ }
466
+ // Auto-fix address in config if missing or mismatched
467
+ if (!address || address.toLowerCase() !== derivedAddress.toLowerCase()) {
468
+ cfg.wallet.address = derivedAddress;
469
+ saveConfig(cfg);
470
+ }
471
+ // All good
472
+ const result = {
473
+ status: "ready",
474
+ address: derivedAddress,
475
+ hasKeystore: true,
476
+ passwordSet: true,
477
+ };
478
+ if (isHeadless()) {
479
+ writeJsonSuccess(result);
480
+ }
481
+ else {
482
+ successBox("Wallet Ready", `Address: ${colors.address(derivedAddress)}\n` +
483
+ `Keystore: ${colors.success("OK")}\n` +
484
+ `Password: ${colors.success("OK")}`);
485
+ }
486
+ });
487
+ // echo wallet export-key
488
+ wallet
489
+ .command("export-key")
490
+ .description("Export decrypted private key (manual-only, blocked in headless)")
491
+ .option("--to-file <path>", "Write private key to file (chmod 600)")
492
+ .option("--stdout", "Print to stdout")
493
+ .option("--i-understand", "Acknowledge risk of printing to stdout")
494
+ .action(async (opts) => {
495
+ // Guardrail: block in headless mode
496
+ if (isHeadless()) {
497
+ throw new EchoError(ErrorCodes.EXPORT_BLOCKED_HEADLESS, "export-key is disabled in headless/agent mode.", "This command is for manual use only. Run it in a terminal.");
498
+ }
499
+ // Must specify mode
500
+ if (!opts.toFile && !opts.stdout) {
501
+ throw new EchoError(ErrorCodes.EXPORT_REQUIRES_ACKNOWLEDGE, "Specify --to-file <path> or --stdout --i-understand.", "Example: echo wallet export-key --to-file ./my-key.txt");
502
+ }
503
+ // --stdout requires --i-understand and TTY
504
+ if (opts.stdout) {
505
+ if (!opts.iUnderstand) {
506
+ throw new EchoError(ErrorCodes.EXPORT_REQUIRES_ACKNOWLEDGE, "--stdout requires --i-understand flag.", "Add --i-understand to confirm you want the key printed to terminal.");
507
+ }
508
+ if (!isStdoutTTY()) {
509
+ throw new EchoError(ErrorCodes.EXPORT_BLOCKED_HEADLESS, "--stdout is only available in TTY mode.", "Use --to-file instead.");
510
+ }
511
+ }
512
+ // Decrypt
513
+ const password = requireKeystorePassword();
514
+ const keystore = loadKeystore();
515
+ if (!keystore) {
516
+ throw new EchoError(ErrorCodes.KEYSTORE_NOT_FOUND, "Keystore not found.", "Run: echo wallet create --json");
517
+ }
518
+ const pk = decryptPrivateKey(keystore, password);
519
+ if (opts.toFile) {
520
+ const fd = opts.toFile;
521
+ writeFileSync(fd, pk, { encoding: "utf-8", mode: platform() !== "win32" ? 0o600 : undefined });
522
+ successBox("Key Exported", `Written to: ${fd}\n${platform() !== "win32" ? "File permissions set to 600." : "Ensure this file is not accessible to other users."}`);
523
+ }
524
+ else if (opts.stdout) {
525
+ writeStdout(pk);
526
+ warnBox("Key Printed", "Private key was printed to stdout. Clear your terminal history.");
527
+ }
528
+ });
529
+ // echo wallet backup
530
+ const backupCmd = wallet.command("backup").description("Backup wallet keystore and config");
531
+ backupCmd
532
+ .action(async () => {
533
+ const backupPath = await autoBackup();
534
+ if (!backupPath) {
535
+ if (isHeadless()) {
536
+ writeJsonSuccess({ status: "nothing_to_backup", hint: "No keystore or config files found." });
537
+ }
538
+ else {
539
+ infoBox("Nothing to Backup", "No keystore.json or config.json found.");
540
+ }
541
+ return;
542
+ }
543
+ if (isHeadless()) {
544
+ writeJsonSuccess({ status: "created", path: backupPath });
545
+ }
546
+ else {
547
+ successBox("Backup Created", `Path: ${colors.info(backupPath)}`);
548
+ }
549
+ });
550
+ // echo wallet backup list
551
+ backupCmd
552
+ .command("list")
553
+ .description("List all backups")
554
+ .action(async () => {
555
+ const backups = listBackups();
556
+ if (isHeadless()) {
557
+ writeJsonSuccess({
558
+ backups: backups.map((b) => ({
559
+ path: b.dir,
560
+ createdAt: b.manifest.createdAt,
561
+ walletAddress: b.manifest.walletAddress,
562
+ files: b.manifest.files,
563
+ })),
564
+ count: backups.length,
565
+ });
566
+ return;
567
+ }
568
+ if (backups.length === 0) {
569
+ infoBox("No Backups", "No backups found. Run: echo wallet backup");
570
+ return;
571
+ }
572
+ writeStderr("");
573
+ infoBox("Wallet Backups", `${backups.length} backup(s) found`);
574
+ const rows = backups.map((b, i) => [
575
+ colors.muted((i + 1).toString()),
576
+ colors.info(b.manifest.createdAt),
577
+ colors.address(b.manifest.walletAddress ?? "n/a"),
578
+ colors.muted(b.manifest.files.join(", ")),
579
+ ]);
580
+ printTable([
581
+ { header: "#", width: 4 },
582
+ { header: "Created", width: 28 },
583
+ { header: "Address", width: 46 },
584
+ { header: "Files", width: 26 },
585
+ ], rows);
586
+ });
587
+ // echo wallet restore <backupDir>
588
+ wallet
589
+ .command("restore <backupDir>")
590
+ .description("Restore wallet from a backup directory")
591
+ .option("--force", "Required: confirm restore (overwrites current files)")
592
+ .action(async (backupDir, opts) => {
593
+ // Must have --force
594
+ if (!opts.force) {
595
+ throw new EchoError(ErrorCodes.CONFIRMATION_REQUIRED, "Restore requires --force flag.", "This will overwrite current keystore and config. Use --force to confirm.");
596
+ }
597
+ // Validate backup dir
598
+ const manifestPath = join(backupDir, "manifest.json");
599
+ if (!existsSync(manifestPath)) {
600
+ throw new EchoError(ErrorCodes.BACKUP_NOT_FOUND, `No manifest.json found in ${backupDir}.`, "Provide a valid backup directory (e.g. from echo wallet backup list).");
601
+ }
602
+ let manifest;
603
+ try {
604
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
605
+ }
606
+ catch {
607
+ throw new EchoError(ErrorCodes.BACKUP_NOT_FOUND, "Failed to parse backup manifest.", "Backup may be corrupted.");
608
+ }
609
+ // Validate manifest.files
610
+ if (!Array.isArray(manifest.files) || !manifest.files.every((f) => typeof f === "string")) {
611
+ throw new EchoError(ErrorCodes.BACKUP_NOT_FOUND, "Invalid manifest: files must be an array of strings.", "Backup may be corrupted.");
612
+ }
613
+ const ALLOWED_RESTORE_FILES = new Set(["keystore.json", "config.json"]);
614
+ // Auto-backup current state before restore
615
+ await autoBackup();
616
+ // Ensure config directory exists
617
+ mkdirSync(CONFIG_DIR, { recursive: true });
618
+ // Copy files from backup to CONFIG_DIR
619
+ for (const file of manifest.files) {
620
+ // Reject path traversal and non-allowlisted files
621
+ if (file.includes("/") || file.includes("\\") || file.includes("..")) {
622
+ throw new EchoError(ErrorCodes.BACKUP_NOT_FOUND, `Invalid file path in manifest: ${file}`, "Backup may be malicious.");
623
+ }
624
+ if (!ALLOWED_RESTORE_FILES.has(file)) {
625
+ throw new EchoError(ErrorCodes.BACKUP_NOT_FOUND, `Unexpected file in backup manifest: ${file}`, "Only keystore.json and config.json can be restored.");
626
+ }
627
+ const src = join(backupDir, file);
628
+ const dst = join(CONFIG_DIR, file);
629
+ if (!existsSync(src)) {
630
+ throw new EchoError(ErrorCodes.BACKUP_NOT_FOUND, `Missing file in backup: ${file}`, "Backup may be corrupted or incomplete.");
631
+ }
632
+ cpSync(src, dst);
633
+ }
634
+ const cfg = loadConfig();
635
+ const result = {
636
+ status: "restored",
637
+ address: cfg.wallet.address ?? null,
638
+ chainId: cfg.chain.chainId,
639
+ restoredFiles: manifest.files,
640
+ backupDir,
641
+ };
642
+ if (isHeadless()) {
643
+ writeJsonSuccess(result);
644
+ }
645
+ else {
646
+ successBox("Wallet Restored", `Address: ${colors.address(cfg.wallet.address ?? "unknown")}\n` +
647
+ `Files: ${manifest.files.join(", ")}\n` +
648
+ `From: ${colors.info(backupDir)}\n\n` +
649
+ colors.muted("Current state was auto-backed up before restore."));
650
+ }
651
+ });
652
+ // echo wallet tokens (subcommand group)
653
+ const tokens = wallet.command("tokens").description("Manage token watchlist");
654
+ // echo wallet tokens add <address>
655
+ tokens
656
+ .command("add <address>")
657
+ .description("Add token to watchlist")
658
+ .action(async (tokenAddress) => {
659
+ // Validate address
660
+ if (!isAddress(tokenAddress)) {
661
+ throw new EchoError(ErrorCodes.INVALID_ADDRESS, `Invalid address: ${tokenAddress}`);
662
+ }
663
+ const checksumAddr = getAddress(tokenAddress);
664
+ const cfg = loadConfig();
665
+ // Check if already in watchlist
666
+ if (cfg.watchlist.tokens.includes(checksumAddr)) {
667
+ if (isHeadless()) {
668
+ writeJsonSuccess({ address: checksumAddr, status: "already_exists", count: cfg.watchlist.tokens.length });
669
+ }
670
+ else {
671
+ warnBox("Already Added", `${colors.address(checksumAddr)} is already in your watchlist`);
672
+ }
673
+ return;
674
+ }
675
+ // Check limit
676
+ if (cfg.watchlist.tokens.length >= MAX_WATCHLIST_TOKENS) {
677
+ throw new EchoError(ErrorCodes.WATCHLIST_FULL, `Watchlist full: maximum ${MAX_WATCHLIST_TOKENS} tokens allowed.`, "Remove some tokens first with: echo wallet tokens remove <address>");
678
+ }
679
+ cfg.watchlist.tokens.push(checksumAddr);
680
+ saveConfig(cfg);
681
+ if (isHeadless()) {
682
+ writeJsonSuccess({ address: checksumAddr, status: "added", count: cfg.watchlist.tokens.length });
683
+ }
684
+ else {
685
+ successBox("Token Added", `${colors.address(checksumAddr)}\n\n` +
686
+ `Watchlist now has ${colors.info(cfg.watchlist.tokens.length.toString())} token(s)`);
687
+ }
688
+ });
689
+ // echo wallet tokens remove <address>
690
+ tokens
691
+ .command("remove <address>")
692
+ .description("Remove token from watchlist")
693
+ .action(async (tokenAddress) => {
694
+ // Validate address
695
+ if (!isAddress(tokenAddress)) {
696
+ throw new EchoError(ErrorCodes.INVALID_ADDRESS, `Invalid address: ${tokenAddress}`);
697
+ }
698
+ const checksumAddr = getAddress(tokenAddress);
699
+ const cfg = loadConfig();
700
+ const index = cfg.watchlist.tokens.findIndex((t) => t.toLowerCase() === checksumAddr.toLowerCase());
701
+ if (index === -1) {
702
+ if (isHeadless()) {
703
+ writeJsonSuccess({ address: checksumAddr, status: "not_found", count: cfg.watchlist.tokens.length });
704
+ }
705
+ else {
706
+ warnBox("Not Found", `${colors.address(checksumAddr)} is not in your watchlist`);
707
+ }
708
+ return;
709
+ }
710
+ cfg.watchlist.tokens.splice(index, 1);
711
+ saveConfig(cfg);
712
+ if (isHeadless()) {
713
+ writeJsonSuccess({ address: checksumAddr, status: "removed", count: cfg.watchlist.tokens.length });
714
+ }
715
+ else {
716
+ successBox("Token Removed", `${colors.address(checksumAddr)}\n\n` +
717
+ `Watchlist now has ${colors.info(cfg.watchlist.tokens.length.toString())} token(s)`);
718
+ }
719
+ });
720
+ // echo wallet tokens list
721
+ tokens
722
+ .command("list")
723
+ .description("List watchlist tokens")
724
+ .action(async () => {
725
+ const cfg = loadConfig();
726
+ if (isHeadless()) {
727
+ writeJsonSuccess({ tokens: cfg.watchlist.tokens, count: cfg.watchlist.tokens.length });
728
+ return;
729
+ }
730
+ if (cfg.watchlist.tokens.length === 0) {
731
+ infoBox("Empty Watchlist", `No tokens in watchlist.\n\n` +
732
+ `Add tokens with: ${colors.info("echo wallet tokens add <address>")}`);
733
+ return;
734
+ }
735
+ writeStderr("");
736
+ infoBox("Token Watchlist", `${cfg.watchlist.tokens.length} token(s)`);
737
+ const rows = cfg.watchlist.tokens.map((addr, i) => [
738
+ colors.muted((i + 1).toString()),
739
+ colors.address(addr),
740
+ ]);
741
+ printTable([
742
+ { header: "#", width: 5 },
743
+ { header: "Address", width: 46 },
744
+ ], rows);
745
+ });
746
+ return wallet;
747
+ }
748
+ //# sourceMappingURL=wallet.js.map