@pasosdejesus/m 0.15.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 (324) hide show
  1. package/dist/blockchain/deployments.d.ts +40 -0
  2. package/dist/blockchain/deployments.d.ts.map +1 -0
  3. package/dist/blockchain/deployments.js +64 -0
  4. package/dist/debug/DebugConsole.d.ts +22 -0
  5. package/dist/debug/DebugConsole.d.ts.map +1 -0
  6. package/dist/debug/DebugConsole.js +79 -0
  7. package/dist/debug/Logger.d.ts +38 -0
  8. package/dist/debug/Logger.d.ts.map +1 -0
  9. package/dist/debug/Logger.js +94 -0
  10. package/dist/debug/index.d.ts +6 -0
  11. package/dist/debug/index.d.ts.map +1 -0
  12. package/dist/debug/index.js +4 -0
  13. package/dist/debug/useLogger.d.ts +14 -0
  14. package/dist/debug/useLogger.d.ts.map +1 -0
  15. package/dist/debug/useLogger.js +23 -0
  16. package/dist/debug/utils.d.ts +10 -0
  17. package/dist/debug/utils.d.ts.map +1 -0
  18. package/dist/debug/utils.js +53 -0
  19. package/dist/e2e/assertions.d.ts +5 -0
  20. package/dist/e2e/assertions.d.ts.map +1 -0
  21. package/dist/e2e/assertions.js +18 -0
  22. package/dist/e2e/browser.d.ts +37 -0
  23. package/dist/e2e/browser.d.ts.map +1 -0
  24. package/dist/e2e/browser.js +97 -0
  25. package/dist/e2e/env.d.ts +18 -0
  26. package/dist/e2e/env.d.ts.map +1 -0
  27. package/dist/e2e/env.js +57 -0
  28. package/dist/e2e/index.d.ts +11 -0
  29. package/dist/e2e/index.d.ts.map +1 -0
  30. package/dist/e2e/index.js +8 -0
  31. package/dist/e2e/navigation.d.ts +4 -0
  32. package/dist/e2e/navigation.d.ts.map +1 -0
  33. package/dist/e2e/navigation.js +32 -0
  34. package/dist/e2e/siwe-mock.d.ts +11 -0
  35. package/dist/e2e/siwe-mock.d.ts.map +1 -0
  36. package/dist/e2e/siwe-mock.js +112 -0
  37. package/dist/e2e/siwe.d.ts +15 -0
  38. package/dist/e2e/siwe.d.ts.map +1 -0
  39. package/dist/e2e/siwe.js +85 -0
  40. package/dist/e2e/wallet-mock.d.ts +13 -0
  41. package/dist/e2e/wallet-mock.d.ts.map +1 -0
  42. package/dist/e2e/wallet-mock.js +91 -0
  43. package/dist/i18n/common-cli.d.ts +11 -0
  44. package/dist/i18n/common-cli.d.ts.map +1 -0
  45. package/dist/i18n/common-cli.js +22 -0
  46. package/dist/i18n/createTranslator.d.ts +28 -0
  47. package/dist/i18n/createTranslator.d.ts.map +1 -0
  48. package/dist/i18n/createTranslator.js +48 -0
  49. package/dist/i18n/index.d.ts +4 -0
  50. package/dist/i18n/index.d.ts.map +1 -0
  51. package/dist/i18n/index.js +2 -0
  52. package/dist/i18n/locales/cli.d.ts +3 -0
  53. package/dist/i18n/locales/cli.d.ts.map +1 -0
  54. package/dist/i18n/locales/cli.js +180 -0
  55. package/dist/i18n/types.d.ts +3 -0
  56. package/dist/i18n/types.d.ts.map +1 -0
  57. package/dist/i18n/types.js +1 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +15 -0
  61. package/dist/index.test.d.ts +2 -0
  62. package/dist/index.test.d.ts.map +1 -0
  63. package/dist/index.test.js +10 -0
  64. package/dist/m.d.ts +2 -0
  65. package/dist/m.d.ts.map +1 -0
  66. package/dist/m.js +334 -0
  67. package/dist/plugin.d.ts +10 -0
  68. package/dist/plugin.d.ts.map +1 -0
  69. package/dist/plugin.js +7 -0
  70. package/dist/shadcn_components/cn.d.ts +13 -0
  71. package/dist/shadcn_components/cn.d.ts.map +1 -0
  72. package/dist/shadcn_components/cn.js +15 -0
  73. package/dist/shadcn_components/ui/accordion.d.ts +8 -0
  74. package/dist/shadcn_components/ui/accordion.d.ts.map +1 -0
  75. package/dist/shadcn_components/ui/accordion.js +14 -0
  76. package/dist/shadcn_components/ui/alert-dialog.d.ts +21 -0
  77. package/dist/shadcn_components/ui/alert-dialog.d.ts.map +1 -0
  78. package/dist/shadcn_components/ui/alert-dialog.js +27 -0
  79. package/dist/shadcn_components/ui/alert.d.ts +9 -0
  80. package/dist/shadcn_components/ui/alert.d.ts.map +1 -0
  81. package/dist/shadcn_components/ui/alert.js +22 -0
  82. package/dist/shadcn_components/ui/avatar.d.ts +7 -0
  83. package/dist/shadcn_components/ui/avatar.d.ts.map +1 -0
  84. package/dist/shadcn_components/ui/avatar.js +12 -0
  85. package/dist/shadcn_components/ui/badge.d.ts +10 -0
  86. package/dist/shadcn_components/ui/badge.d.ts.map +1 -0
  87. package/dist/shadcn_components/ui/badge.js +22 -0
  88. package/dist/shadcn_components/ui/button.d.ts +11 -0
  89. package/dist/shadcn_components/ui/button.d.ts.map +1 -0
  90. package/dist/shadcn_components/ui/button.js +31 -0
  91. package/dist/shadcn_components/ui/card.d.ts +10 -0
  92. package/dist/shadcn_components/ui/card.d.ts.map +1 -0
  93. package/dist/shadcn_components/ui/card.js +24 -0
  94. package/dist/shadcn_components/ui/checkbox.d.ts +5 -0
  95. package/dist/shadcn_components/ui/checkbox.d.ts.map +1 -0
  96. package/dist/shadcn_components/ui/checkbox.js +9 -0
  97. package/dist/shadcn_components/ui/dialog.d.ts +20 -0
  98. package/dist/shadcn_components/ui/dialog.d.ts.map +1 -0
  99. package/dist/shadcn_components/ui/dialog.js +23 -0
  100. package/dist/shadcn_components/ui/dropdown-menu.d.ts +28 -0
  101. package/dist/shadcn_components/ui/dropdown-menu.d.ts.map +1 -0
  102. package/dist/shadcn_components/ui/dropdown-menu.js +36 -0
  103. package/dist/shadcn_components/ui/form.d.ts +24 -0
  104. package/dist/shadcn_components/ui/form.d.ts.map +1 -0
  105. package/dist/shadcn_components/ui/form.js +63 -0
  106. package/dist/shadcn_components/ui/input.d.ts +4 -0
  107. package/dist/shadcn_components/ui/input.d.ts.map +1 -0
  108. package/dist/shadcn_components/ui/input.js +6 -0
  109. package/dist/shadcn_components/ui/label.d.ts +6 -0
  110. package/dist/shadcn_components/ui/label.d.ts.map +1 -0
  111. package/dist/shadcn_components/ui/label.js +10 -0
  112. package/dist/shadcn_components/ui/menubar.d.ts +29 -0
  113. package/dist/shadcn_components/ui/menubar.d.ts.map +1 -0
  114. package/dist/shadcn_components/ui/menubar.js +46 -0
  115. package/dist/shadcn_components/ui/popover.d.ts +7 -0
  116. package/dist/shadcn_components/ui/popover.d.ts.map +1 -0
  117. package/dist/shadcn_components/ui/popover.js +10 -0
  118. package/dist/shadcn_components/ui/progress.d.ts +5 -0
  119. package/dist/shadcn_components/ui/progress.d.ts.map +1 -0
  120. package/dist/shadcn_components/ui/progress.js +8 -0
  121. package/dist/shadcn_components/ui/radio-group.d.ts +6 -0
  122. package/dist/shadcn_components/ui/radio-group.d.ts.map +1 -0
  123. package/dist/shadcn_components/ui/radio-group.js +15 -0
  124. package/dist/shadcn_components/ui/scroll-area.d.ts +6 -0
  125. package/dist/shadcn_components/ui/scroll-area.d.ts.map +1 -0
  126. package/dist/shadcn_components/ui/scroll-area.js +12 -0
  127. package/dist/shadcn_components/ui/select.d.ts +14 -0
  128. package/dist/shadcn_components/ui/select.d.ts.map +1 -0
  129. package/dist/shadcn_components/ui/select.js +27 -0
  130. package/dist/shadcn_components/ui/separator.d.ts +5 -0
  131. package/dist/shadcn_components/ui/separator.d.ts.map +1 -0
  132. package/dist/shadcn_components/ui/separator.js +8 -0
  133. package/dist/shadcn_components/ui/sheet.d.ts +26 -0
  134. package/dist/shadcn_components/ui/sheet.d.ts.map +1 -0
  135. package/dist/shadcn_components/ui/sheet.js +37 -0
  136. package/dist/shadcn_components/ui/skeleton.d.ts +3 -0
  137. package/dist/shadcn_components/ui/skeleton.d.ts.map +1 -0
  138. package/dist/shadcn_components/ui/skeleton.js +6 -0
  139. package/dist/shadcn_components/ui/slider.d.ts +5 -0
  140. package/dist/shadcn_components/ui/slider.d.ts.map +1 -0
  141. package/dist/shadcn_components/ui/slider.js +8 -0
  142. package/dist/shadcn_components/ui/switch.d.ts +5 -0
  143. package/dist/shadcn_components/ui/switch.d.ts.map +1 -0
  144. package/dist/shadcn_components/ui/switch.js +8 -0
  145. package/dist/shadcn_components/ui/table.d.ts +11 -0
  146. package/dist/shadcn_components/ui/table.d.ts.map +1 -0
  147. package/dist/shadcn_components/ui/table.js +20 -0
  148. package/dist/shadcn_components/ui/tabs.d.ts +8 -0
  149. package/dist/shadcn_components/ui/tabs.d.ts.map +1 -0
  150. package/dist/shadcn_components/ui/tabs.js +13 -0
  151. package/dist/shadcn_components/ui/textarea.d.ts +4 -0
  152. package/dist/shadcn_components/ui/textarea.d.ts.map +1 -0
  153. package/dist/shadcn_components/ui/textarea.js +8 -0
  154. package/dist/shadcn_components/ui/toast.d.ts +16 -0
  155. package/dist/shadcn_components/ui/toast.d.ts.map +1 -0
  156. package/dist/shadcn_components/ui/toast.js +34 -0
  157. package/dist/shadcn_components/ui/toaster.d.ts +2 -0
  158. package/dist/shadcn_components/ui/toaster.d.ts.map +1 -0
  159. package/dist/shadcn_components/ui/toaster.js +10 -0
  160. package/dist/shadcn_components/ui/tooltip.d.ts +8 -0
  161. package/dist/shadcn_components/ui/tooltip.d.ts.map +1 -0
  162. package/dist/shadcn_components/ui/tooltip.js +11 -0
  163. package/dist/shadcn_components/ui/use-toast.d.ts +45 -0
  164. package/dist/shadcn_components/ui/use-toast.d.ts.map +1 -0
  165. package/dist/shadcn_components/ui/use-toast.js +126 -0
  166. package/dist/tasks/context.d.ts +13 -0
  167. package/dist/tasks/context.d.ts.map +1 -0
  168. package/dist/tasks/context.js +310 -0
  169. package/dist/tasks/create.d.ts +2 -0
  170. package/dist/tasks/create.d.ts.map +1 -0
  171. package/dist/tasks/create.js +91 -0
  172. package/dist/tasks/db.d.ts +19 -0
  173. package/dist/tasks/db.d.ts.map +1 -0
  174. package/dist/tasks/db.js +791 -0
  175. package/dist/tasks/e2e.d.ts +2 -0
  176. package/dist/tasks/e2e.d.ts.map +1 -0
  177. package/dist/tasks/e2e.js +124 -0
  178. package/dist/tasks/edit.d.ts +7 -0
  179. package/dist/tasks/edit.d.ts.map +1 -0
  180. package/dist/tasks/edit.js +471 -0
  181. package/dist/tasks/engine.d.ts +2 -0
  182. package/dist/tasks/engine.d.ts.map +1 -0
  183. package/dist/tasks/engine.js +149 -0
  184. package/dist/tasks/env-diff.d.ts +2 -0
  185. package/dist/tasks/env-diff.d.ts.map +1 -0
  186. package/dist/tasks/env-diff.js +140 -0
  187. package/dist/tasks/fuzzy.d.ts +9 -0
  188. package/dist/tasks/fuzzy.d.ts.map +1 -0
  189. package/dist/tasks/fuzzy.js +153 -0
  190. package/dist/tasks/glob.d.ts +5 -0
  191. package/dist/tasks/glob.d.ts.map +1 -0
  192. package/dist/tasks/glob.js +57 -0
  193. package/dist/tasks/grep.d.ts +9 -0
  194. package/dist/tasks/grep.d.ts.map +1 -0
  195. package/dist/tasks/grep.js +93 -0
  196. package/dist/tasks/readfile.d.ts +2 -0
  197. package/dist/tasks/readfile.d.ts.map +1 -0
  198. package/dist/tasks/readfile.js +177 -0
  199. package/dist/tasks/update.d.ts +5 -0
  200. package/dist/tasks/update.d.ts.map +1 -0
  201. package/dist/tasks/update.js +200 -0
  202. package/dist/tasks/util.d.ts +12 -0
  203. package/dist/tasks/util.d.ts.map +1 -0
  204. package/dist/tasks/util.js +42 -0
  205. package/dist/tasks/wallet.d.ts +2 -0
  206. package/dist/tasks/wallet.d.ts.map +1 -0
  207. package/dist/tasks/wallet.js +697 -0
  208. package/dist/tasks/writefile.d.ts +2 -0
  209. package/dist/tasks/writefile.d.ts.map +1 -0
  210. package/dist/tasks/writefile.js +66 -0
  211. package/dist/templates/AGENTS.md.hbs +81 -0
  212. package/dist/templates/ARCHITECTURE.md.hbs +146 -0
  213. package/dist/templates/CONTRIBUTING.md.hbs +83 -0
  214. package/dist/templates/LICENCIA.md +21 -0
  215. package/dist/templates/LICENSE.md +15 -0
  216. package/dist/templates/Makefile +16 -0
  217. package/dist/templates/README.md.hbs +111 -0
  218. package/dist/templates/apps/.env.example.hbs +75 -0
  219. package/dist/templates/apps/hardhat/.prettierrc +16 -0
  220. package/dist/templates/apps/hardhat/MOCK_USDT_README.md +20 -0
  221. package/dist/templates/apps/hardhat/Makefile +13 -0
  222. package/dist/templates/apps/hardhat/README.md +129 -0
  223. package/dist/templates/apps/hardhat/bin/MusdtVerification +4 -0
  224. package/dist/templates/apps/hardhat/bin/approveDepositOwner +7 -0
  225. package/dist/templates/apps/hardhat/bin/contractVerification +9 -0
  226. package/dist/templates/apps/hardhat/bin/deployMockUSDT +7 -0
  227. package/dist/templates/apps/hardhat/bin/prepadJ.sh +14 -0
  228. package/dist/templates/apps/hardhat/bin/verifyMockUSDT +7 -0
  229. package/dist/templates/apps/hardhat/contracts/MockUSDT.sol +47 -0
  230. package/dist/templates/apps/hardhat/hardhat.config.ts +102 -0
  231. package/dist/templates/apps/hardhat/package.json +55 -0
  232. package/dist/templates/apps/hardhat/scripts/approveDepositOwner.ts +22 -0
  233. package/dist/templates/apps/hardhat/scripts/deployMockUSDT.ts +49 -0
  234. package/dist/templates/apps/hardhat/scripts/verifyDeployment.ts +93 -0
  235. package/dist/templates/apps/hardhat/scripts/verifyMockUSDT.ts +52 -0
  236. package/dist/templates/apps/hardhat/sync-abis.mjs +58 -0
  237. package/dist/templates/apps/hardhat/test/MockUSDT.test.ts +40 -0
  238. package/dist/templates/apps/hardhat/tsconfig.json +27 -0
  239. package/dist/templates/apps/nextjs/.config/kysely.config.ts +36 -0
  240. package/dist/templates/apps/nextjs/Makefile +24 -0
  241. package/dist/templates/apps/nextjs/README.md.hbs +76 -0
  242. package/dist/templates/apps/nextjs/abis/IERC20.json +74 -0
  243. package/dist/templates/apps/nextjs/abis/MockUSDT.json +242 -0
  244. package/dist/templates/apps/nextjs/abis/ReentrancyGuard.json +1 -0
  245. package/dist/templates/apps/nextjs/app/globals.css +3 -0
  246. package/dist/templates/apps/nextjs/app/layout.tsx.hbs +26 -0
  247. package/dist/templates/apps/nextjs/app/page.tsx.hbs +225 -0
  248. package/dist/templates/apps/nextjs/app/testui/page.tsx +371 -0
  249. package/dist/templates/apps/nextjs/bin/m +8 -0
  250. package/dist/templates/apps/nextjs/components.json +21 -0
  251. package/dist/templates/apps/nextjs/next-env.d.ts +5 -0
  252. package/dist/templates/apps/nextjs/next.config.ts +10 -0
  253. package/dist/templates/apps/nextjs/package.json.hbs +48 -0
  254. package/dist/templates/apps/nextjs/pnpm-workspace.yaml +13 -0
  255. package/dist/templates/apps/nextjs/postcss.config.mjs +6 -0
  256. package/dist/templates/apps/nextjs/tailwind.config.js +11 -0
  257. package/dist/templates/apps/nextjs/tests/helpers.ts +31 -0
  258. package/dist/templates/apps/nextjs/tests/index.test.ts +7 -0
  259. package/dist/templates/apps/nextjs/tests/setup-env.ts.hbs +8 -0
  260. package/dist/templates/apps/nextjs/tests/test-utils.example.test.ts +168 -0
  261. package/dist/templates/apps/nextjs/tsconfig.json +44 -0
  262. package/dist/templates/apps/nextjs/tsconfig.test.json +38 -0
  263. package/dist/templates/apps/nextjs/vitest.config.ts +13 -0
  264. package/dist/test-utils/fs-mocks.d.ts +32 -0
  265. package/dist/test-utils/fs-mocks.d.ts.map +1 -0
  266. package/dist/test-utils/fs-mocks.js +63 -0
  267. package/dist/test-utils/i18n-mock.d.ts +21 -0
  268. package/dist/test-utils/i18n-mock.d.ts.map +1 -0
  269. package/dist/test-utils/i18n-mock.js +29 -0
  270. package/dist/test-utils/index.d.ts +12 -0
  271. package/dist/test-utils/index.d.ts.map +1 -0
  272. package/dist/test-utils/index.js +11 -0
  273. package/dist/test-utils/kysely-mocks.d.ts +298 -0
  274. package/dist/test-utils/kysely-mocks.d.ts.map +1 -0
  275. package/dist/test-utils/kysely-mocks.js +173 -0
  276. package/dist/test-utils/radix-mocks.d.ts +134 -0
  277. package/dist/test-utils/radix-mocks.d.ts.map +1 -0
  278. package/dist/test-utils/radix-mocks.js +175 -0
  279. package/dist/test-utils/rainbowkit-mocks.d.ts +80 -0
  280. package/dist/test-utils/rainbowkit-mocks.d.ts.map +1 -0
  281. package/dist/test-utils/rainbowkit-mocks.js +247 -0
  282. package/dist/test-utils/viem-mocks.d.ts +86 -0
  283. package/dist/test-utils/viem-mocks.d.ts.map +1 -0
  284. package/dist/test-utils/viem-mocks.js +54 -0
  285. package/dist/util/cli-parser.d.ts +20 -0
  286. package/dist/util/cli-parser.d.ts.map +1 -0
  287. package/dist/util/cli-parser.js +38 -0
  288. package/dist/util/colors.d.ts +5 -0
  289. package/dist/util/colors.d.ts.map +1 -0
  290. package/dist/util/colors.js +5 -0
  291. package/dist/util/locale.d.ts +3 -0
  292. package/dist/util/locale.d.ts.map +1 -0
  293. package/dist/util/locale.js +10 -0
  294. package/dist/wba/client.d.ts +19 -0
  295. package/dist/wba/client.d.ts.map +1 -0
  296. package/dist/wba/client.js +31 -0
  297. package/dist/wba/index.d.ts +4 -0
  298. package/dist/wba/index.d.ts.map +1 -0
  299. package/dist/wba/index.js +2 -0
  300. package/dist/wba/routes/event.d.ts +32 -0
  301. package/dist/wba/routes/event.d.ts.map +1 -0
  302. package/dist/wba/routes/event.js +42 -0
  303. package/dist/wba/routes/index.d.ts +3 -0
  304. package/dist/wba/routes/index.d.ts.map +1 -0
  305. package/dist/wba/routes/index.js +2 -0
  306. package/dist/wba/routes/summary.d.ts +44 -0
  307. package/dist/wba/routes/summary.d.ts.map +1 -0
  308. package/dist/wba/routes/summary.js +146 -0
  309. package/dist/wba/server.d.ts +18 -0
  310. package/dist/wba/server.d.ts.map +1 -0
  311. package/dist/wba/server.js +29 -0
  312. package/dist/wba/types.d.ts +68 -0
  313. package/dist/wba/types.d.ts.map +1 -0
  314. package/dist/wba/types.js +6 -0
  315. package/package.json +223 -0
  316. package/src/blockchain/README.md +80 -0
  317. package/src/debug/README.md +94 -0
  318. package/src/e2e/README.md +105 -0
  319. package/src/i18n/README.md +143 -0
  320. package/src/shadcn_components/README.md +125 -0
  321. package/src/tasks/README.md +69 -0
  322. package/src/test-utils/README.md +638 -0
  323. package/src/util/README.md +18 -0
  324. package/src/wba/README.md +175 -0
@@ -0,0 +1,697 @@
1
+ // src/tasks/wallet.ts
2
+ // Wallet management commands: generate, approve, list, send, balance, etc.
3
+ //
4
+ // Wallets stored in ~/.m/wallets/ with chmod 600.
5
+ // Uses viem for all blockchain interactions.
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import os from 'os';
9
+ import { success, warn, problem } from '../util/colors.js';
10
+ import { createTranslator } from '../i18n/createTranslator.js';
11
+ import { detectLocale } from '../util/locale.js';
12
+ import commonCli from '../i18n/common-cli.js';
13
+ const t = createTranslator(detectLocale(), {
14
+ en: {
15
+ 'wallet.unknown': 'Unknown command: wallet:{{0}}',
16
+ 'wallet.no_wallets': 'No wallets found. Use wallet:generate or wallet:import first.',
17
+ },
18
+ es: {
19
+ 'wallet.unknown': 'Comando desconocido: wallet:{{0}}',
20
+ 'wallet.no_wallets': 'No se encontraron billeteras. Use wallet:generate o wallet:import primero.',
21
+ },
22
+ }, commonCli);
23
+ // ==================== STORAGE ====================
24
+ function getWalletsDir() {
25
+ return process.env.M_WALLETS_DIR || path.join(os.homedir(), '.m', 'wallets');
26
+ }
27
+ function ensureWalletDir() {
28
+ fs.mkdirSync(getWalletsDir(), { recursive: true, mode: 0o700 });
29
+ }
30
+ function ensurePermissions(filePath) {
31
+ try {
32
+ const stat = fs.statSync(filePath);
33
+ if (stat.isDirectory() && (stat.mode & 0o777) !== 0o700) {
34
+ warn(`Fixing permissions on ${filePath} (was ${(stat.mode & 0o777).toString(8)}, setting 700)`);
35
+ fs.chmodSync(filePath, 0o700);
36
+ }
37
+ else if (stat.isFile() && (stat.mode & 0o777) !== 0o600) {
38
+ warn(`Fixing permissions on ${filePath} (was ${(stat.mode & 0o777).toString(8)}, setting 600)`);
39
+ fs.chmodSync(filePath, 0o600);
40
+ }
41
+ }
42
+ catch { /* file may not exist yet */ }
43
+ }
44
+ function getWalletPath(name) {
45
+ return path.join(getWalletsDir(), `${name}.json`);
46
+ }
47
+ function loadWallet(name) {
48
+ const p = getWalletPath(name);
49
+ if (!fs.existsSync(p))
50
+ return null;
51
+ ensurePermissions(p);
52
+ return JSON.parse(fs.readFileSync(p, 'utf-8'));
53
+ }
54
+ function saveWallet(name, address, privateKey, mnemonic) {
55
+ ensureWalletDir();
56
+ ensurePermissions(getWalletsDir());
57
+ const p = getWalletPath(name);
58
+ const data = { name, address, privateKey };
59
+ if (mnemonic)
60
+ data.mnemonic = mnemonic;
61
+ fs.writeFileSync(p, JSON.stringify(data, null, 2), { mode: 0o600 });
62
+ ensurePermissions(p);
63
+ }
64
+ // ==================== HELPERS ====================
65
+ function getArg(args, name) {
66
+ const idx = args.indexOf(name);
67
+ return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : null;
68
+ }
69
+ /**
70
+ * Produce a user-friendly error message from an RPC failure.
71
+ * Detects common viem/HTTP errors: missing API keys, timeouts, network issues.
72
+ */
73
+ function formatRpcError(err, rpc) {
74
+ const msg = err instanceof Error ? err.message : String(err);
75
+ if (msg.includes('403') || msg.includes('Forbidden'))
76
+ return `RPC unavailable — the endpoint may require an API key. Try --rpc with a valid URL.\n URL: ${rpc}`;
77
+ if (msg.includes('401') || msg.includes('Unauthorized'))
78
+ return `RPC authentication failed — check your API key.\n URL: ${rpc}`;
79
+ if (msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND') || msg.includes('ETIMEDOUT'))
80
+ return `Cannot reach RPC — network error. Verify the URL or your connection.\n URL: ${rpc}`;
81
+ if (msg.includes('HTTP error'))
82
+ return `RPC error: ${msg}\n URL: ${rpc}`;
83
+ return `RPC error: ${msg}\n URL: ${rpc}`;
84
+ }
85
+ /**
86
+ * Parse a function signature like "pause()" or "transfer(address,uint256)"
87
+ * with optional comma-separated args "0x1234, 100".
88
+ */
89
+ function parseFunctionSignature(sig, argsStr) {
90
+ const match = sig.match(/^(\w+)\(([^)]*)\)$/);
91
+ if (!match)
92
+ throw new Error(`Invalid function signature: ${sig}. Use "name(type1,type2,...)"`);
93
+ const name = match[1];
94
+ const typeStr = match[2];
95
+ const types = typeStr ? typeStr.split(',').map(t => t.trim()).filter(Boolean) : [];
96
+ if (!argsStr) {
97
+ if (types.length > 0)
98
+ throw new Error(`--args required for function ${name}(${types.join(',')})`);
99
+ return { name, types, args: [] };
100
+ }
101
+ const rawArgs = argsStr.split(',').map(a => a.trim());
102
+ const parsedArgs = types.map((t, i) => {
103
+ const raw = rawArgs[i];
104
+ if (!raw)
105
+ throw new Error(`Missing arg ${i} (type: ${t}) for function ${name}`);
106
+ if (t.startsWith('uint') || t.startsWith('int'))
107
+ return BigInt(raw);
108
+ if (t === 'bool')
109
+ return raw === 'true';
110
+ return raw;
111
+ });
112
+ return { name, types, args: parsedArgs };
113
+ }
114
+ async function resolveToAddress(value) {
115
+ if (value.startsWith('0x'))
116
+ return value;
117
+ const w = loadWallet(value);
118
+ if (w)
119
+ return w.address;
120
+ throw new Error(`Wallet not found and not a valid address: ${value}`);
121
+ }
122
+ // ==================== wallet:generate ====================
123
+ async function cmdGenerate(args) {
124
+ const name = getArg(args, '--name');
125
+ const index = parseInt(getArg(args, '--index') || '0');
126
+ const outputEnv = getArg(args, '--output-env');
127
+ if (!name)
128
+ throw new Error('--name required');
129
+ const targetPath = getWalletPath(name);
130
+ if (fs.existsSync(targetPath)) {
131
+ throw new Error(`Wallet "${name}" already exists at ${targetPath}. Use a different --name.`);
132
+ }
133
+ const { generateMnemonic, mnemonicToAccount, english } = await import('viem/accounts');
134
+ const mnemonic = generateMnemonic(english);
135
+ const account = mnemonicToAccount(mnemonic, { accountIndex: index, addressIndex: 0 });
136
+ const hdKey = account.getHdKey();
137
+ const privateKey = `0x${Buffer.from(hdKey.privateKey).toString('hex')}`;
138
+ saveWallet(name, account.address, privateKey, mnemonic);
139
+ success(`\n✅ Wallet generated: ${name}`);
140
+ console.log(` Address: ${account.address}`);
141
+ console.log(` PrivateKey: ${privateKey}`);
142
+ console.log(` Mnemonic: ${mnemonic}`);
143
+ warn(` ⚠️ SAVE THE MNEMONIC — it will not be shown again.`);
144
+ console.log(` Stored: ${targetPath}`);
145
+ if (outputEnv) {
146
+ console.log(`\n${outputEnv}=${account.address}`);
147
+ }
148
+ }
149
+ // ==================== wallet:approve ====================
150
+ async function cmdApprove(args) {
151
+ const name = getArg(args, '--name');
152
+ const token = getArg(args, '--token');
153
+ const spenderRaw = getArg(args, '--spender');
154
+ const amount = getArg(args, '--amount');
155
+ const network = getArg(args, '--network') || 'celoSepolia';
156
+ const rpcUrl = getArg(args, '--rpc');
157
+ if (!name || !token || !spenderRaw || !amount) {
158
+ throw new Error('--name, --token, --spender, --amount required');
159
+ }
160
+ const wallet = loadWallet(name);
161
+ if (!wallet)
162
+ throw new Error(`Wallet "${name}" not found. Use wallet:generate or wallet:import first.`);
163
+ if (!wallet.privateKey)
164
+ throw new Error(`Wallet "${name}" has no private key stored.`);
165
+ const spender = await resolveToAddress(spenderRaw);
166
+ const { createPublicClient, createWalletClient, http, parseUnits } = await import('viem');
167
+ const { privateKeyToAccount } = await import('viem/accounts');
168
+ const account = privateKeyToAccount(wallet.privateKey);
169
+ const chain = await resolveChain(network);
170
+ const rpc = rpcUrl || getDefaultRpc(network);
171
+ const pc = createPublicClient({ chain, transport: http(rpc) });
172
+ const wc = createWalletClient({ chain, transport: http(rpc), account });
173
+ // Read token decimals (with fallback)
174
+ let decimals = 6;
175
+ try {
176
+ decimals = await pc.readContract({
177
+ address: token,
178
+ abi: [{ name: 'decimals', type: 'function', outputs: [{ type: 'uint8' }] }],
179
+ functionName: 'decimals',
180
+ });
181
+ }
182
+ catch {
183
+ // Some tokens (proxies, non-standard) don't expose decimals. Default to 6.
184
+ }
185
+ const maxUint256 = '115792089237316195423570985008687907853269984665640564039457584007913129639935';
186
+ const parsedAmount = amount === 'max'
187
+ ? BigInt(maxUint256)
188
+ : parseUnits(amount, decimals);
189
+ const erc20Abi = [{
190
+ name: 'approve',
191
+ type: 'function',
192
+ inputs: [
193
+ { name: 'spender', type: 'address' },
194
+ { name: 'value', type: 'uint256' },
195
+ ],
196
+ outputs: [{ type: 'bool' }],
197
+ }];
198
+ const hash = await wc.writeContract({
199
+ address: token,
200
+ abi: erc20Abi,
201
+ functionName: 'approve',
202
+ args: [spender, parsedAmount],
203
+ chain,
204
+ account,
205
+ });
206
+ success(`✅ Approved ${amount} tokens for spender ${spender}`);
207
+ console.log(` TX: ${hash}`);
208
+ }
209
+ // ==================== CHAIN RESOLUTION ====================
210
+ async function resolveChain(network) {
211
+ const chains = {
212
+ celo: () => import('viem/chains').then(m => m.celo),
213
+ celoSepolia: () => import('viem/chains').then(m => m.celoSepolia),
214
+ celoAlfajores: () => import('viem/chains').then(m => m.celoAlfajores),
215
+ base: () => import('viem/chains').then(m => m.base),
216
+ baseSepolia: () => import('viem/chains').then(m => m.baseSepolia),
217
+ };
218
+ const loader = chains[network];
219
+ if (!loader)
220
+ throw new Error(`Unknown network: ${network}. Use celo, celoSepolia, base, or baseSepolia.`);
221
+ return loader();
222
+ }
223
+ function getDefaultRpc(network) {
224
+ const fromEnv = process.env.NEXT_PUBLIC_RPC_URL || process.env.RPC_URL;
225
+ if (fromEnv)
226
+ return fromEnv;
227
+ const defaults = {
228
+ celo: 'https://forno.celo.org',
229
+ celoSepolia: 'https://forno.celo-sepolia.celo-testnet.org',
230
+ celoAlfajores: 'https://alfajores-forno.celo-testnet.org',
231
+ base: 'https://mainnet.base.org',
232
+ baseSepolia: 'https://sepolia.base.org',
233
+ };
234
+ return defaults[network] || '';
235
+ }
236
+ // ==================== wallet:import ====================
237
+ async function cmdImport(args) {
238
+ const name = getArg(args, '--name');
239
+ const mnemonic = getArg(args, '--mnemonic');
240
+ const privateKey = getArg(args, '--private-key');
241
+ const index = parseInt(getArg(args, '--index') || '0');
242
+ if (!name)
243
+ throw new Error('--name required');
244
+ if (!mnemonic && !privateKey)
245
+ throw new Error('--mnemonic or --private-key required');
246
+ const targetPath = getWalletPath(name);
247
+ if (fs.existsSync(targetPath))
248
+ throw new Error(`Wallet "${name}" already exists.`);
249
+ let address;
250
+ let pk;
251
+ if (privateKey) {
252
+ const { privateKeyToAddress } = await import('viem/accounts');
253
+ pk = privateKey;
254
+ address = privateKeyToAddress(privateKey);
255
+ }
256
+ else {
257
+ const { mnemonicToAccount } = await import('viem/accounts');
258
+ const account = mnemonicToAccount(mnemonic, { accountIndex: index, addressIndex: 0 });
259
+ const hdKey = account.getHdKey();
260
+ pk = `0x${Buffer.from(hdKey.privateKey).toString('hex')}`;
261
+ address = account.address;
262
+ }
263
+ saveWallet(name, address, pk);
264
+ success(`✅ Wallet imported: ${name}`);
265
+ console.log(` Address: ${address}`);
266
+ }
267
+ // ==================== wallet:list ====================
268
+ async function cmdList(args) {
269
+ const nameFilter = getArg(args, '--name');
270
+ const addressOnly = args.includes('--address-only');
271
+ const showBalance = args.includes('--balance');
272
+ const network = getArg(args, '--network') || 'celoSepolia';
273
+ const rpcUrl = getArg(args, '--rpc');
274
+ ensureWalletDir();
275
+ ensurePermissions(getWalletsDir());
276
+ const files = fs.readdirSync(getWalletsDir()).filter(f => f.endsWith('.json'));
277
+ if (files.length === 0) {
278
+ console.log('No wallets found. Use wallet:generate or wallet:import to create one.');
279
+ return;
280
+ }
281
+ for (const file of files) {
282
+ const w = JSON.parse(fs.readFileSync(path.join(getWalletsDir(), file), 'utf-8'));
283
+ if (nameFilter && w.name !== nameFilter)
284
+ continue;
285
+ if (addressOnly) {
286
+ console.log(w.address);
287
+ }
288
+ else if (showBalance) {
289
+ const chain = await resolveChain(network);
290
+ const rpc = rpcUrl || getDefaultRpc(network);
291
+ const { createPublicClient, http, formatEther } = await import('viem');
292
+ const pc = createPublicClient({ chain, transport: http(rpc) });
293
+ try {
294
+ const bal = await pc.getBalance({ address: w.address });
295
+ console.log(`${w.name.padEnd(20)} ${w.address} ${formatEther(bal)} ${chain.nativeCurrency?.symbol || 'ETH'}`);
296
+ }
297
+ catch (err) {
298
+ console.log(`${w.name.padEnd(20)} ${w.address} (balance unavailable: ${formatRpcError(err, rpc)})`);
299
+ }
300
+ }
301
+ else {
302
+ console.log(`${w.name.padEnd(20)} ${w.address}`);
303
+ }
304
+ }
305
+ }
306
+ // ==================== wallet:send ====================
307
+ async function cmdSend(args) {
308
+ const name = getArg(args, '--name');
309
+ const toRaw = getArg(args, '--to');
310
+ const amount = getArg(args, '--amount');
311
+ const token = getArg(args, '--token');
312
+ const rawData = getArg(args, '--data');
313
+ const func = getArg(args, '--function');
314
+ const funcArgs = getArg(args, '--args');
315
+ const network = getArg(args, '--network') || 'celoSepolia';
316
+ const rpcUrl = getArg(args, '--rpc');
317
+ if (!name || !toRaw)
318
+ throw new Error('--name, --to required');
319
+ if (!amount && !token && !rawData && !func)
320
+ throw new Error('--amount, --token, --data, or --function required');
321
+ // Validate function signature early (before wallet lookup)
322
+ if (func) {
323
+ parseFunctionSignature(func, funcArgs);
324
+ }
325
+ // Validate function signature early (before wallet lookup)
326
+ let parsedFunc = null;
327
+ if (func) {
328
+ parsedFunc = parseFunctionSignature(func, funcArgs);
329
+ }
330
+ const wallet = loadWallet(name);
331
+ if (!wallet)
332
+ throw new Error(`Wallet "${name}" not found.`);
333
+ if (!wallet.privateKey)
334
+ throw new Error(`Wallet "${name}" has no private key.`);
335
+ const to = await resolveToAddress(toRaw);
336
+ const { createPublicClient, createWalletClient, http, parseUnits, encodeFunctionData, formatEther } = await import('viem');
337
+ const { privateKeyToAccount } = await import('viem/accounts');
338
+ const account = privateKeyToAccount(wallet.privateKey);
339
+ const chain = await resolveChain(network);
340
+ const rpc = rpcUrl || getDefaultRpc(network);
341
+ if (!rpc)
342
+ throw new Error(`No RPC URL for network "${network}". Provide --rpc.`);
343
+ // Compute calldata from --function + --args
344
+ let data;
345
+ if (parsedFunc) {
346
+ const { name, types, args } = parsedFunc;
347
+ data = encodeFunctionData({
348
+ abi: [{ name, type: 'function', inputs: types.map(t => ({ type: t })) }],
349
+ functionName: name,
350
+ args,
351
+ });
352
+ }
353
+ else if (rawData) {
354
+ data = rawData;
355
+ }
356
+ if (data) {
357
+ // Raw contract call (arbitrary function via --data or --function + --args)
358
+ const wc = createWalletClient({ chain, transport: http(rpc), account });
359
+ const hash = await wc.sendTransaction({
360
+ to, data, chain, account,
361
+ });
362
+ console.log(`✅ Transaction sent to ${to}`);
363
+ console.log(` TX: ${hash}`);
364
+ }
365
+ else if (token) {
366
+ if (!amount)
367
+ throw new Error('--amount required for token transfers');
368
+ // ERC20 transfer
369
+ const pc = createPublicClient({ chain, transport: http(rpc) });
370
+ const decimals = await pc.readContract({
371
+ address: token,
372
+ abi: [{ name: 'decimals', type: 'function', outputs: [{ type: 'uint8' }] }],
373
+ functionName: 'decimals',
374
+ });
375
+ const parsedAmount = parseUnits(amount, decimals);
376
+ const wc = createWalletClient({ chain, transport: http(rpc), account });
377
+ const hash = await wc.writeContract({
378
+ address: token,
379
+ abi: [{ name: 'transfer', type: 'function', inputs: [
380
+ { name: 'to', type: 'address' }, { name: 'value', type: 'uint256' }
381
+ ], outputs: [{ type: 'bool' }] }],
382
+ functionName: 'transfer',
383
+ args: [to, parsedAmount],
384
+ chain, account,
385
+ });
386
+ console.log(`✅ Sent ${amount} token(s) to ${to}`);
387
+ console.log(` TX: ${hash}`);
388
+ }
389
+ else {
390
+ if (!amount)
391
+ throw new Error('--amount required for native transfers');
392
+ // Native token transfer
393
+ const wc = createWalletClient({ chain, transport: http(rpc), account });
394
+ const hash = await wc.sendTransaction({
395
+ to,
396
+ value: parseUnits(amount, 18),
397
+ chain, account,
398
+ });
399
+ const pc = createPublicClient({ chain, transport: http(rpc) });
400
+ const receipt = await pc.waitForTransactionReceipt({ hash });
401
+ console.log(`✅ Sent ${amount} ${chain.nativeCurrency?.symbol || 'ETH'} to ${to}`);
402
+ console.log(` TX: ${hash}`);
403
+ }
404
+ }
405
+ // ==================== wallet:balance ====================
406
+ async function cmdBalance(args) {
407
+ const name = getArg(args, '--name');
408
+ const token = getArg(args, '--token');
409
+ const network = getArg(args, '--network') || 'celoSepolia';
410
+ const rpcUrl = getArg(args, '--rpc');
411
+ if (!name)
412
+ throw new Error('--name required');
413
+ const wallet = loadWallet(name);
414
+ if (!wallet)
415
+ throw new Error(`Wallet "${name}" not found.`);
416
+ const { createPublicClient, http, formatEther } = await import('viem');
417
+ const chain = await resolveChain(network);
418
+ const rpc = rpcUrl || getDefaultRpc(network);
419
+ const pc = createPublicClient({ chain, transport: http(rpc) });
420
+ if (token) {
421
+ try {
422
+ const bal = await pc.readContract({
423
+ address: token,
424
+ abi: [{ name: 'balanceOf', type: 'function', inputs: [{ type: 'address' }], outputs: [{ type: 'uint256' }] }],
425
+ functionName: 'balanceOf',
426
+ args: [wallet.address],
427
+ });
428
+ const decimals = await pc.readContract({
429
+ address: token,
430
+ abi: [{ name: 'decimals', type: 'function', outputs: [{ type: 'uint8' }] }],
431
+ functionName: 'decimals',
432
+ });
433
+ const { formatUnits } = await import('viem');
434
+ console.log(`${formatUnits(bal, decimals)} tokens`);
435
+ }
436
+ catch (err) {
437
+ problem(formatRpcError(err, rpc));
438
+ }
439
+ }
440
+ else {
441
+ try {
442
+ const bal = await pc.getBalance({ address: wallet.address });
443
+ console.log(`${formatEther(bal)} ${chain.nativeCurrency?.symbol || 'ETH'}`);
444
+ }
445
+ catch (err) {
446
+ problem(formatRpcError(err, rpc));
447
+ }
448
+ }
449
+ }
450
+ // ==================== wallet:call ====================
451
+ async function cmdCall(args) {
452
+ const name = getArg(args, '--name');
453
+ const to = getArg(args, '--to');
454
+ const func = getArg(args, '--function');
455
+ const funcArgs = getArg(args, '--args');
456
+ const network = getArg(args, '--network') || 'celoSepolia';
457
+ const rpcUrl = getArg(args, '--rpc');
458
+ if (!name || !to || !func)
459
+ throw new Error('--name, --to, --function required');
460
+ const wallet = loadWallet(name);
461
+ if (!wallet)
462
+ throw new Error(`Wallet "${name}" not found.`);
463
+ if (!wallet.privateKey)
464
+ throw new Error(`Wallet "${name}" has no private key.`);
465
+ const { createPublicClient, http } = await import('viem');
466
+ const { privateKeyToAccount } = await import('viem/accounts');
467
+ const chain = await resolveChain(network);
468
+ const rpc = rpcUrl || getDefaultRpc(network);
469
+ if (!rpc)
470
+ throw new Error(`No RPC URL for network "${network}". Provide --rpc.`);
471
+ const account = privateKeyToAccount(wallet.privateKey);
472
+ const pc = createPublicClient({ chain, transport: http(rpc) });
473
+ // Parse function signature: "balanceOf(address)" or "totalSupply()(uint256)"
474
+ const match = func.match(/^(\w+)\(([^)]*)\)(?:\((\w+)\))?$/);
475
+ if (!match)
476
+ throw new Error(`Invalid function signature: ${func}. Use "name(type1,type2,...)" or "name()(returnType)"`);
477
+ const funcName = match[1];
478
+ const typeStr = match[2];
479
+ const returnType = match[3] || 'address'; // default: address (most common)
480
+ const types = typeStr ? typeStr.split(',').map(t => t.trim()).filter(Boolean) : [];
481
+ // Parse args
482
+ const parsedArgs = funcArgs ? funcArgs.split(',').map(a => a.trim()) : [];
483
+ const result = await pc.readContract({
484
+ address: to,
485
+ abi: [{ name: funcName, type: 'function', inputs: types.map((t, i) => ({ type: t })), outputs: [{ type: returnType }], stateMutability: 'view' }],
486
+ functionName: funcName,
487
+ args: parsedArgs,
488
+ });
489
+ console.log(result);
490
+ }
491
+ // ==================== wallet:derive ====================
492
+ async function cmdDerive(args) {
493
+ const parentName = getArg(args, '--name');
494
+ const asName = getArg(args, '--as');
495
+ const index = parseInt(getArg(args, '--index') || '1');
496
+ if (!parentName || !asName)
497
+ throw new Error('--name and --as required');
498
+ const parent = loadWallet(parentName);
499
+ if (!parent)
500
+ throw new Error(`Wallet "${parentName}" not found.`);
501
+ const parentMnemonic = parent.mnemonic;
502
+ if (!parentMnemonic) {
503
+ throw new Error(`Cannot derive from "${parentName}" — no mnemonic stored. ` +
504
+ `Re-generate the wallet with wallet:generate.`);
505
+ }
506
+ const targetPath = getWalletPath(asName);
507
+ if (fs.existsSync(targetPath)) {
508
+ throw new Error(`Wallet "${asName}" already exists at ${targetPath}. Use a different --as.`);
509
+ }
510
+ const { mnemonicToAccount } = await import('viem/accounts');
511
+ const account = mnemonicToAccount(parentMnemonic, { accountIndex: index, addressIndex: 0 });
512
+ const hdKey = account.getHdKey();
513
+ const privateKey = `0x${Buffer.from(hdKey.privateKey).toString('hex')}`;
514
+ saveWallet(asName, account.address, privateKey, parentMnemonic);
515
+ success(`\n✅ Wallet derived: ${asName}`);
516
+ console.log(` Address: ${account.address}`);
517
+ console.log(` PrivateKey: ${privateKey}`);
518
+ console.log(` Derived from: ${parentName} (index ${index})`);
519
+ console.log(` Stored: ${targetPath}`);
520
+ }
521
+ // ==================== wallet:sign (airgapped) ====================
522
+ async function cmdSign(args) {
523
+ const name = getArg(args, '--name');
524
+ const txFile = getArg(args, '--tx');
525
+ if (!name)
526
+ throw new Error('--name required');
527
+ if (!txFile)
528
+ throw new Error('--tx <json-file> required');
529
+ const wallet = loadWallet(name);
530
+ if (!wallet)
531
+ throw new Error(`Wallet "${name}" not found.`);
532
+ if (!wallet.privateKey)
533
+ throw new Error(`Wallet "${name}" has no private key.`);
534
+ const tx = JSON.parse(fs.readFileSync(txFile, 'utf-8'));
535
+ const { privateKeyToAccount } = await import('viem/accounts');
536
+ const account = privateKeyToAccount(wallet.privateKey);
537
+ // Accept hex or decimal for gas/chainId
538
+ const signed = await account.signTransaction({
539
+ to: tx.to,
540
+ value: tx.value ? BigInt(tx.value) : undefined,
541
+ data: tx.data || undefined,
542
+ chainId: typeof tx.chainId === 'string' ? parseInt(tx.chainId) : tx.chainId,
543
+ nonce: tx.nonce,
544
+ gasLimit: tx.gasLimit ? BigInt(tx.gasLimit) : undefined,
545
+ gasPrice: tx.gasPrice ? BigInt(tx.gasPrice) : undefined,
546
+ maxFeePerGas: tx.maxFeePerGas ? BigInt(tx.maxFeePerGas) : undefined,
547
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas ? BigInt(tx.maxPriorityFeePerGas) : undefined,
548
+ });
549
+ const signedPath = txFile.replace(/\.json$/, '-signed.json');
550
+ fs.writeFileSync(signedPath, JSON.stringify({ signedTx: signed }, null, 2));
551
+ success(`✅ Signed transaction saved to ${signedPath}`);
552
+ }
553
+ // ==================== wallet:broadcast ====================
554
+ async function cmdBroadcast(args) {
555
+ const signedFile = getArg(args, '--signed-tx');
556
+ const rpcUrl = getArg(args, '--rpc');
557
+ if (!signedFile)
558
+ throw new Error('--signed-tx <json> required');
559
+ if (!rpcUrl)
560
+ throw new Error('--rpc <url> required');
561
+ const data = JSON.parse(fs.readFileSync(signedFile, 'utf-8'));
562
+ if (!data.signedTx)
563
+ throw new Error('Signed transaction file must contain { signedTx: "0x..." }');
564
+ const { createPublicClient, http } = await import('viem');
565
+ const client = createPublicClient({ transport: http(rpcUrl) });
566
+ const hash = await client.sendRawTransaction({ serializedTransaction: data.signedTx });
567
+ success(`✅ Transaction broadcasted`);
568
+ console.log(` Hash: ${hash}`);
569
+ }
570
+ // ==================== KECCAK ====================
571
+ async function cmdKeccak(args) {
572
+ if (args.length === 0) {
573
+ throw new Error('Usage: bin/m wallet:keccak <string>');
574
+ }
575
+ const input = args.join(' ');
576
+ const { keccak256, toHex } = await import('viem');
577
+ const hash = keccak256(toHex(input));
578
+ console.log(hash);
579
+ }
580
+ // ==================== USAGE ====================
581
+ function usage(command) {
582
+ if (command === 'send') {
583
+ console.log('Usage: bin/m wallet:send --name <n> --to <addr|name> [--amount <eth>] [--token <addr>] [--function "name(type,...)"] [--args "v1,v2"] [--data 0x...] [--network celoSepolia] [--rpc <url>]');
584
+ return;
585
+ }
586
+ if (command === 'generate') {
587
+ console.log('Usage: bin/m wallet:generate --name <n> [--index 0] [--output-env <VAR>]');
588
+ return;
589
+ }
590
+ if (command === 'approve') {
591
+ console.log('Usage: bin/m wallet:approve --name <n> --token <addr> --spender <addr|name> --amount <u> [--network celoSepolia] [--rpc <url>]');
592
+ return;
593
+ }
594
+ if (command === 'list') {
595
+ console.log('Usage: bin/m wallet:list [--name <n>] [--address-only] [--balance] [--network celoSepolia] [--rpc <url>]');
596
+ return;
597
+ }
598
+ if (command === 'import') {
599
+ console.log('Usage: bin/m wallet:import --name <n> [--mnemonic "..."] [--private-key 0x...] [--index 0]');
600
+ return;
601
+ }
602
+ if (command === 'balance') {
603
+ console.log('Usage: bin/m wallet:balance --name <n> [--token <addr>] [--network celoSepolia] [--rpc <url>]');
604
+ return;
605
+ }
606
+ if (command === 'sign') {
607
+ console.log('Usage: bin/m wallet:sign --name <n> --tx <json-file>');
608
+ return;
609
+ }
610
+ if (command === 'broadcast') {
611
+ console.log('Usage: bin/m wallet:broadcast --signed-tx <json> [--rpc <url>]');
612
+ return;
613
+ }
614
+ if (command === 'keccak') {
615
+ console.log('Usage: bin/m wallet:keccak <string>');
616
+ return;
617
+ }
618
+ if (command === 'call') {
619
+ console.log('Usage: bin/m wallet:call --name <n> --to <addr> --function "name(type,...)" [--args "v1,v2"] [--network celoSepolia] [--rpc <url>]');
620
+ return;
621
+ }
622
+ console.log('Wallet commands:');
623
+ console.log(' bin/m wallet:generate --name <n> [--index 0] [--output-env <VAR>]');
624
+ console.log(' bin/m wallet:approve --name <n> --token <addr> --spender <addr|name> --amount <u> [--network celoSepolia] [--rpc <url>]');
625
+ console.log(' bin/m wallet:list [--name <n>] [--address-only] [--balance]');
626
+ console.log(' bin/m wallet:import --name <n> [--mnemonic "..."] [--private-key 0x...] [--index 0]');
627
+ console.log(' bin/m wallet:send --name <n> --to <addr|name> [--amount <eth>] [--token <addr>] [--function "name(type,...)"] [--args "v1,v2"] [--data 0x...]');
628
+ console.log(' bin/m wallet:balance --name <n> [--token <addr>]');
629
+ console.log(' bin/m wallet:sign --name <n> --tx <json-file>');
630
+ console.log(' bin/m wallet:broadcast --signed-tx <json> [--rpc <url>]');
631
+ console.log(' bin/m wallet:keccak <string>');
632
+ console.log(' bin/m wallet:call --name <n> --to <addr> --function "name(type,...)" [--args "v1,v2"]');
633
+ }
634
+ // ==================== ENTRY POINT ====================
635
+ export async function runWallet(args) {
636
+ if (args.length === 0) {
637
+ usage();
638
+ return;
639
+ }
640
+ // Per-command help: wallet:send --help
641
+ if (args.length >= 2 && (args[1] === '--help' || args[1] === '-h')) {
642
+ usage(args[0]);
643
+ return;
644
+ }
645
+ // Generic help: wallet --help or wallet:send --help (when command is --help directly)
646
+ if (args.includes('--help') || args.includes('-h')) {
647
+ usage();
648
+ return;
649
+ }
650
+ const command = args[0];
651
+ const rest = args.slice(1);
652
+ try {
653
+ switch (command) {
654
+ case 'generate':
655
+ await cmdGenerate(rest);
656
+ break;
657
+ case 'approve':
658
+ await cmdApprove(rest);
659
+ break;
660
+ case 'list':
661
+ await cmdList(rest);
662
+ break;
663
+ case 'import':
664
+ await cmdImport(rest);
665
+ break;
666
+ case 'send':
667
+ await cmdSend(rest);
668
+ break;
669
+ case 'balance':
670
+ await cmdBalance(rest);
671
+ break;
672
+ case 'call':
673
+ await cmdCall(rest);
674
+ break;
675
+ case 'derive':
676
+ await cmdDerive(rest);
677
+ break;
678
+ case 'sign':
679
+ await cmdSign(rest);
680
+ break;
681
+ case 'broadcast':
682
+ await cmdBroadcast(rest);
683
+ break;
684
+ case 'keccak':
685
+ await cmdKeccak(rest);
686
+ break;
687
+ default:
688
+ problem(t('wallet.unknown', command));
689
+ usage();
690
+ throw new Error(t('wallet.unknown', command));
691
+ }
692
+ }
693
+ catch (err) {
694
+ problem(err.message || String(err));
695
+ throw err;
696
+ }
697
+ }