gnoman 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (606) hide show
  1. package/.eslintrc.cjs +24 -0
  2. package/.gnoman/contracts.json +4094 -0
  3. package/.gnoman/exec_package/runtime-debug.jsonl +45 -0
  4. package/.gnoman/holds.sqlite +0 -0
  5. package/.gnoman/license.json +7 -0
  6. package/.gnoman/safes.json +37 -0
  7. package/.gnoman/vanity-jobs.json +67 -0
  8. package/.gnoman/wallets.db +0 -0
  9. package/.prettierrc.json +6 -0
  10. package/CODex_TASKS.md +124 -0
  11. package/LICENSE.md +164 -0
  12. package/README.md +95 -0
  13. package/assets/GnoLogo.jpg +0 -0
  14. package/assets/self.png +0 -0
  15. package/backend/controllers/contractController.ts +49 -0
  16. package/backend/controllers/devToolsController.ts +76 -0
  17. package/backend/controllers/etherscanController.ts +59 -0
  18. package/backend/controllers/historyController.ts +7 -0
  19. package/backend/controllers/keyringController.ts +134 -0
  20. package/backend/controllers/robinhoodController.ts +80 -0
  21. package/backend/controllers/safeController.ts +167 -0
  22. package/backend/controllers/sandboxController.ts +63 -0
  23. package/backend/controllers/settingsController.ts +38 -0
  24. package/backend/controllers/walletController.ts +151 -0
  25. package/backend/index.ts +133 -0
  26. package/backend/licenses/license_public.pem +3 -0
  27. package/backend/licenses/verify_license.py +43 -0
  28. package/backend/routes/contractRoutes.ts +11 -0
  29. package/backend/routes/devToolsRoutes.ts +11 -0
  30. package/backend/routes/etherscanRoutes.ts +11 -0
  31. package/backend/routes/historyRoutes.ts +8 -0
  32. package/backend/routes/keyringRoutes.ts +25 -0
  33. package/backend/routes/licenseRoutes.ts +35 -0
  34. package/backend/routes/robinhoodRoutes.ts +22 -0
  35. package/backend/routes/runtimeRoutes.ts +29 -0
  36. package/backend/routes/safeRoutes.ts +28 -0
  37. package/backend/routes/sandboxRoutes.ts +17 -0
  38. package/backend/routes/settingsRoutes.ts +14 -0
  39. package/backend/routes/walletRoutes.ts +21 -0
  40. package/backend/services/chainlinkService.ts +65 -0
  41. package/backend/services/contractRegistryService.ts +205 -0
  42. package/backend/services/devToolsService.ts +251 -0
  43. package/backend/services/diagnosticsService.ts +350 -0
  44. package/backend/services/etherscanService.ts +152 -0
  45. package/backend/services/historyService.ts +89 -0
  46. package/backend/services/keyringAccessor.ts +4 -0
  47. package/backend/services/licenseService.ts +163 -0
  48. package/backend/services/onchain/abiRegistry.ts +57 -0
  49. package/backend/services/onchain/chainlinkClient.ts +56 -0
  50. package/backend/services/onchain/errors.ts +16 -0
  51. package/backend/services/onchain/etherscanClient.ts +94 -0
  52. package/backend/services/onchain/index.ts +76 -0
  53. package/backend/services/onchain/tenderlyRpcClient.ts +74 -0
  54. package/backend/services/onchain/types.ts +33 -0
  55. package/backend/services/onchainAutomationService.ts +424 -0
  56. package/backend/services/robinhood/auth.ts +42 -0
  57. package/backend/services/robinhood/client.ts +123 -0
  58. package/backend/services/robinhood/integrationService.ts +140 -0
  59. package/backend/services/robinhood/provider.ts +22 -0
  60. package/backend/services/robinhood/unofficialClient.ts +66 -0
  61. package/backend/services/rpcService.ts +44 -0
  62. package/backend/services/runtimeTelemetryService.ts +158 -0
  63. package/backend/services/safeConfigRepository.ts +205 -0
  64. package/backend/services/safeService.ts +588 -0
  65. package/backend/services/sandboxService.ts +157 -0
  66. package/backend/services/secureSettingsService.ts +45 -0
  67. package/backend/services/transactionHoldService.ts +223 -0
  68. package/backend/services/vanityService.ts +293 -0
  69. package/backend/services/walletService.ts +290 -0
  70. package/backend/services/walletStore.ts +179 -0
  71. package/backend/types/express-async-handler.d.ts +13 -0
  72. package/backend/types/keyring.d.ts +19 -0
  73. package/backend/utils/abiResolver.ts +208 -0
  74. package/backend/utils/http.ts +6 -0
  75. package/backend/utils/secretsResolver.ts +150 -0
  76. package/backend/utils/signer.ts +11 -0
  77. package/backend/workers/vanityWorker.ts +76 -0
  78. package/capacitor.config.ts +13 -0
  79. package/cli/gnoman.ts +424 -0
  80. package/contracts/OracleConsumer.sol +20 -0
  81. package/contracts/PriceFeedConsumer.sol +22 -0
  82. package/dist/backend/backend/controllers/contractController.js +41 -0
  83. package/dist/backend/backend/controllers/contractController.js.map +1 -0
  84. package/dist/backend/backend/controllers/devToolsController.js +63 -0
  85. package/dist/backend/backend/controllers/devToolsController.js.map +1 -0
  86. package/dist/backend/backend/controllers/etherscanController.js +53 -0
  87. package/dist/backend/backend/controllers/etherscanController.js.map +1 -0
  88. package/dist/backend/backend/controllers/historyController.js +12 -0
  89. package/dist/backend/backend/controllers/historyController.js.map +1 -0
  90. package/dist/backend/backend/controllers/keyringController.js +126 -0
  91. package/dist/backend/backend/controllers/keyringController.js.map +1 -0
  92. package/dist/backend/backend/controllers/robinhoodController.js +69 -0
  93. package/dist/backend/backend/controllers/robinhoodController.js.map +1 -0
  94. package/dist/backend/backend/controllers/safeController.js +137 -0
  95. package/dist/backend/backend/controllers/safeController.js.map +1 -0
  96. package/dist/backend/backend/controllers/sandboxController.js +48 -0
  97. package/dist/backend/backend/controllers/sandboxController.js.map +1 -0
  98. package/dist/backend/backend/controllers/settingsController.js +34 -0
  99. package/dist/backend/backend/controllers/settingsController.js.map +1 -0
  100. package/dist/backend/backend/controllers/walletController.js +140 -0
  101. package/dist/backend/backend/controllers/walletController.js.map +1 -0
  102. package/dist/backend/backend/index.js +119 -0
  103. package/dist/backend/backend/index.js.map +1 -0
  104. package/dist/backend/backend/routes/contractRoutes.js +44 -0
  105. package/dist/backend/backend/routes/contractRoutes.js.map +1 -0
  106. package/dist/backend/backend/routes/devToolsRoutes.js +44 -0
  107. package/dist/backend/backend/routes/devToolsRoutes.js.map +1 -0
  108. package/dist/backend/backend/routes/etherscanRoutes.js +44 -0
  109. package/dist/backend/backend/routes/etherscanRoutes.js.map +1 -0
  110. package/dist/backend/backend/routes/historyRoutes.js +41 -0
  111. package/dist/backend/backend/routes/historyRoutes.js.map +1 -0
  112. package/dist/backend/backend/routes/keyringRoutes.js +18 -0
  113. package/dist/backend/backend/routes/keyringRoutes.js.map +1 -0
  114. package/dist/backend/backend/routes/licenseRoutes.js +30 -0
  115. package/dist/backend/backend/routes/licenseRoutes.js.map +1 -0
  116. package/dist/backend/backend/routes/robinhoodRoutes.js +14 -0
  117. package/dist/backend/backend/routes/robinhoodRoutes.js.map +1 -0
  118. package/dist/backend/backend/routes/runtimeRoutes.js +26 -0
  119. package/dist/backend/backend/routes/runtimeRoutes.js.map +1 -0
  120. package/dist/backend/backend/routes/safeRoutes.js +61 -0
  121. package/dist/backend/backend/routes/safeRoutes.js.map +1 -0
  122. package/dist/backend/backend/routes/sandboxRoutes.js +50 -0
  123. package/dist/backend/backend/routes/sandboxRoutes.js.map +1 -0
  124. package/dist/backend/backend/routes/settingsRoutes.js +10 -0
  125. package/dist/backend/backend/routes/settingsRoutes.js.map +1 -0
  126. package/dist/backend/backend/routes/walletRoutes.js +54 -0
  127. package/dist/backend/backend/routes/walletRoutes.js.map +1 -0
  128. package/dist/backend/backend/services/chainlinkService.js +48 -0
  129. package/dist/backend/backend/services/chainlinkService.js.map +1 -0
  130. package/dist/backend/backend/services/contractRegistryService.js +138 -0
  131. package/dist/backend/backend/services/contractRegistryService.js.map +1 -0
  132. package/dist/backend/backend/services/devToolsService.js +213 -0
  133. package/dist/backend/backend/services/devToolsService.js.map +1 -0
  134. package/dist/backend/backend/services/diagnosticsService.js +286 -0
  135. package/dist/backend/backend/services/diagnosticsService.js.map +1 -0
  136. package/dist/backend/backend/services/etherscanService.js +125 -0
  137. package/dist/backend/backend/services/etherscanService.js.map +1 -0
  138. package/dist/backend/backend/services/historyService.js +75 -0
  139. package/dist/backend/backend/services/historyService.js.map +1 -0
  140. package/dist/backend/backend/services/keyringAccessor.js +40 -0
  141. package/dist/backend/backend/services/keyringAccessor.js.map +1 -0
  142. package/dist/backend/backend/services/licenseService.js +130 -0
  143. package/dist/backend/backend/services/licenseService.js.map +1 -0
  144. package/dist/backend/backend/services/onchain/abiRegistry.js +47 -0
  145. package/dist/backend/backend/services/onchain/abiRegistry.js.map +1 -0
  146. package/dist/backend/backend/services/onchain/chainlinkClient.js +43 -0
  147. package/dist/backend/backend/services/onchain/chainlinkClient.js.map +1 -0
  148. package/dist/backend/backend/services/onchain/errors.js +13 -0
  149. package/dist/backend/backend/services/onchain/errors.js.map +1 -0
  150. package/dist/backend/backend/services/onchain/etherscanClient.js +82 -0
  151. package/dist/backend/backend/services/onchain/etherscanClient.js.map +1 -0
  152. package/dist/backend/backend/services/onchain/index.js +79 -0
  153. package/dist/backend/backend/services/onchain/index.js.map +1 -0
  154. package/dist/backend/backend/services/onchain/tenderlyRpcClient.js +60 -0
  155. package/dist/backend/backend/services/onchain/tenderlyRpcClient.js.map +1 -0
  156. package/dist/backend/backend/services/onchain/types.js +14 -0
  157. package/dist/backend/backend/services/onchain/types.js.map +1 -0
  158. package/dist/backend/backend/services/onchainAutomationService.js +316 -0
  159. package/dist/backend/backend/services/onchainAutomationService.js.map +1 -0
  160. package/dist/backend/backend/services/robinhood/auth.js +26 -0
  161. package/dist/backend/backend/services/robinhood/auth.js.map +1 -0
  162. package/dist/backend/backend/services/robinhood/client.js +73 -0
  163. package/dist/backend/backend/services/robinhood/client.js.map +1 -0
  164. package/dist/backend/backend/services/robinhood/integrationService.js +119 -0
  165. package/dist/backend/backend/services/robinhood/integrationService.js.map +1 -0
  166. package/dist/backend/backend/services/robinhood/provider.js +17 -0
  167. package/dist/backend/backend/services/robinhood/provider.js.map +1 -0
  168. package/dist/backend/backend/services/robinhood/unofficialClient.js +61 -0
  169. package/dist/backend/backend/services/robinhood/unofficialClient.js.map +1 -0
  170. package/dist/backend/backend/services/rpcService.js +48 -0
  171. package/dist/backend/backend/services/rpcService.js.map +1 -0
  172. package/dist/backend/backend/services/runtimeTelemetryService.js +96 -0
  173. package/dist/backend/backend/services/runtimeTelemetryService.js.map +1 -0
  174. package/dist/backend/backend/services/safeConfigRepository.js +147 -0
  175. package/dist/backend/backend/services/safeConfigRepository.js.map +1 -0
  176. package/dist/backend/backend/services/safeService.js +527 -0
  177. package/dist/backend/backend/services/safeService.js.map +1 -0
  178. package/dist/backend/backend/services/sandboxService.js +135 -0
  179. package/dist/backend/backend/services/sandboxService.js.map +1 -0
  180. package/dist/backend/backend/services/secureSettingsService.js +50 -0
  181. package/dist/backend/backend/services/secureSettingsService.js.map +1 -0
  182. package/dist/backend/backend/services/transactionHoldService.js +184 -0
  183. package/dist/backend/backend/services/transactionHoldService.js.map +1 -0
  184. package/dist/backend/backend/services/vanityService.js +235 -0
  185. package/dist/backend/backend/services/vanityService.js.map +1 -0
  186. package/dist/backend/backend/services/walletService.js +202 -0
  187. package/dist/backend/backend/services/walletService.js.map +1 -0
  188. package/dist/backend/backend/services/walletStore.js +132 -0
  189. package/dist/backend/backend/services/walletStore.js.map +1 -0
  190. package/dist/backend/backend/utils/abiResolver.js +182 -0
  191. package/dist/backend/backend/utils/abiResolver.js.map +1 -0
  192. package/dist/backend/backend/utils/http.js +12 -0
  193. package/dist/backend/backend/utils/http.js.map +1 -0
  194. package/dist/backend/backend/utils/secretsResolver.js +137 -0
  195. package/dist/backend/backend/utils/secretsResolver.js.map +1 -0
  196. package/dist/backend/backend/utils/signer.js +15 -0
  197. package/dist/backend/backend/utils/signer.js.map +1 -0
  198. package/dist/backend/backend/workers/vanityWorker.js +63 -0
  199. package/dist/backend/backend/workers/vanityWorker.js.map +1 -0
  200. package/dist/backend/cli/gnoman.js +387 -0
  201. package/dist/backend/cli/gnoman.js.map +1 -0
  202. package/dist/backend/modules/sandbox/abiLoader.js +78 -0
  203. package/dist/backend/modules/sandbox/abiLoader.js.map +1 -0
  204. package/dist/backend/modules/sandbox/contractSimulator.js +205 -0
  205. package/dist/backend/modules/sandbox/contractSimulator.js.map +1 -0
  206. package/dist/backend/modules/sandbox/formBuilder.js +14 -0
  207. package/dist/backend/modules/sandbox/formBuilder.js.map +1 -0
  208. package/dist/backend/modules/sandbox/index.js +24 -0
  209. package/dist/backend/modules/sandbox/index.js.map +1 -0
  210. package/dist/backend/modules/sandbox/localFork.js +103 -0
  211. package/dist/backend/modules/sandbox/localFork.js.map +1 -0
  212. package/dist/backend/modules/sandbox/sandboxManager.js +130 -0
  213. package/dist/backend/modules/sandbox/sandboxManager.js.map +1 -0
  214. package/dist/backend/modules/sandbox/types.js +3 -0
  215. package/dist/backend/modules/sandbox/types.js.map +1 -0
  216. package/dist/backend/src/core/backends/fileBackend.js +136 -0
  217. package/dist/backend/src/core/backends/fileBackend.js.map +1 -0
  218. package/dist/backend/src/core/backends/memoryBackend.js +26 -0
  219. package/dist/backend/src/core/backends/memoryBackend.js.map +1 -0
  220. package/dist/backend/src/core/backends/systemBackend.js +86 -0
  221. package/dist/backend/src/core/backends/systemBackend.js.map +1 -0
  222. package/dist/backend/src/core/backends/types.js +12 -0
  223. package/dist/backend/src/core/backends/types.js.map +1 -0
  224. package/dist/backend/src/core/keyringManager.js +178 -0
  225. package/dist/backend/src/core/keyringManager.js.map +1 -0
  226. package/dist/backend/src/utils/abiResolver.js +180 -0
  227. package/dist/backend/src/utils/abiResolver.js.map +1 -0
  228. package/dist/backend/src/utils/runtimeObservability.js +78 -0
  229. package/dist/backend/src/utils/runtimeObservability.js.map +1 -0
  230. package/dist/backend/src/utils/secretsResolver.js +138 -0
  231. package/dist/backend/src/utils/secretsResolver.js.map +1 -0
  232. package/dist/cli/backend/services/diagnosticsService.js +286 -0
  233. package/dist/cli/backend/services/diagnosticsService.js.map +1 -0
  234. package/dist/cli/backend/services/keyringAccessor.js +40 -0
  235. package/dist/cli/backend/services/keyringAccessor.js.map +1 -0
  236. package/dist/cli/backend/services/rpcService.js +48 -0
  237. package/dist/cli/backend/services/rpcService.js.map +1 -0
  238. package/dist/cli/backend/services/runtimeTelemetryService.js +96 -0
  239. package/dist/cli/backend/services/runtimeTelemetryService.js.map +1 -0
  240. package/dist/cli/backend/services/walletService.js +202 -0
  241. package/dist/cli/backend/services/walletService.js.map +1 -0
  242. package/dist/cli/backend/services/walletStore.js +132 -0
  243. package/dist/cli/backend/services/walletStore.js.map +1 -0
  244. package/dist/cli/backend/utils/http.js +12 -0
  245. package/dist/cli/backend/utils/http.js.map +1 -0
  246. package/dist/cli/backend/utils/secretsResolver.js +137 -0
  247. package/dist/cli/backend/utils/secretsResolver.js.map +1 -0
  248. package/dist/cli/cli/gnoman.js +387 -0
  249. package/dist/cli/cli/gnoman.js.map +1 -0
  250. package/dist/cli/src/core/backends/fileBackend.js +136 -0
  251. package/dist/cli/src/core/backends/fileBackend.js.map +1 -0
  252. package/dist/cli/src/core/backends/memoryBackend.js +26 -0
  253. package/dist/cli/src/core/backends/memoryBackend.js.map +1 -0
  254. package/dist/cli/src/core/backends/systemBackend.js +86 -0
  255. package/dist/cli/src/core/backends/systemBackend.js.map +1 -0
  256. package/dist/cli/src/core/backends/types.js +12 -0
  257. package/dist/cli/src/core/backends/types.js.map +1 -0
  258. package/dist/cli/src/core/keyringManager.js +178 -0
  259. package/dist/cli/src/core/keyringManager.js.map +1 -0
  260. package/dist/cli/src/utils/abiResolver.js +180 -0
  261. package/dist/cli/src/utils/abiResolver.js.map +1 -0
  262. package/dist/cli/src/utils/runtimeObservability.js +78 -0
  263. package/dist/cli/src/utils/runtimeObservability.js.map +1 -0
  264. package/dist/cli/src/utils/secretsResolver.js +138 -0
  265. package/dist/cli/src/utils/secretsResolver.js.map +1 -0
  266. package/dist/main/backend/services/keyringAccessor.js +40 -0
  267. package/dist/main/backend/services/keyringAccessor.js.map +1 -0
  268. package/dist/main/backend/utils/http.js +12 -0
  269. package/dist/main/backend/utils/http.js.map +1 -0
  270. package/dist/main/main/ipcHandlers/index.js +26 -0
  271. package/dist/main/main/ipcHandlers/index.js.map +1 -0
  272. package/dist/main/main/keyring/keyringmanager.js +101 -0
  273. package/dist/main/main/keyring/keyringmanager.js.map +1 -0
  274. package/dist/main/main/main.js +224 -0
  275. package/dist/main/main/main.js.map +1 -0
  276. package/dist/main/main/preload/index.js +19 -0
  277. package/dist/main/main/preload/index.js.map +1 -0
  278. package/dist/main/main/preload/licenseBridge.js +105 -0
  279. package/dist/main/main/preload/licenseBridge.js.map +1 -0
  280. package/dist/main/src/core/backends/fileBackend.js +136 -0
  281. package/dist/main/src/core/backends/fileBackend.js.map +1 -0
  282. package/dist/main/src/core/backends/memoryBackend.js +26 -0
  283. package/dist/main/src/core/backends/memoryBackend.js.map +1 -0
  284. package/dist/main/src/core/backends/systemBackend.js +86 -0
  285. package/dist/main/src/core/backends/systemBackend.js.map +1 -0
  286. package/dist/main/src/core/backends/types.js +12 -0
  287. package/dist/main/src/core/backends/types.js.map +1 -0
  288. package/dist/main/src/core/keyringManager.js +178 -0
  289. package/dist/main/src/core/keyringManager.js.map +1 -0
  290. package/dist/main/src/utils/abiResolver.js +180 -0
  291. package/dist/main/src/utils/abiResolver.js.map +1 -0
  292. package/dist/main/src/utils/runtimeObservability.js +78 -0
  293. package/dist/main/src/utils/runtimeObservability.js.map +1 -0
  294. package/dist/main/src/utils/secretsResolver.js +138 -0
  295. package/dist/main/src/utils/secretsResolver.js.map +1 -0
  296. package/docs/development-guide.md +203 -0
  297. package/docs/etherscan-chainlink-integration.md +44 -0
  298. package/docs/gnoman-20-user-manual-STANDARD-PRINT-READY.pdf +0 -0
  299. package/docs/gnoman-20-user-manual-STANDARD.pdf +0 -0
  300. package/docs/license-dev-guide.md +106 -0
  301. package/docs/robinhood-integration.md +30 -0
  302. package/docs/system-audit-gpt-guide.md +208 -0
  303. package/docs/system-robustness-audit.md +50 -0
  304. package/docs/user-guide.md +73 -0
  305. package/docs/wiki/development-guide.md +203 -0
  306. package/docs/wiki/license-dev-guide.md +106 -0
  307. package/docs/wiki/user-guide.md +73 -0
  308. package/eslint.config.js +85 -0
  309. package/gnoman2.0/.eslintrc.cjs +24 -0
  310. package/gnoman2.0/.prettierrc.json +6 -0
  311. package/gnoman2.0/CODex_TASKS.md +124 -0
  312. package/gnoman2.0/LICENSE.md +164 -0
  313. package/gnoman2.0/README.md +95 -0
  314. package/gnoman2.0/assets/GnoLogo.jpg +0 -0
  315. package/gnoman2.0/assets/self.png +0 -0
  316. package/gnoman2.0/backend/controllers/contractController.ts +49 -0
  317. package/gnoman2.0/backend/controllers/devToolsController.ts +76 -0
  318. package/gnoman2.0/backend/controllers/etherscanController.ts +59 -0
  319. package/gnoman2.0/backend/controllers/historyController.ts +7 -0
  320. package/gnoman2.0/backend/controllers/keyringController.ts +134 -0
  321. package/gnoman2.0/backend/controllers/robinhoodController.ts +80 -0
  322. package/gnoman2.0/backend/controllers/safeController.ts +167 -0
  323. package/gnoman2.0/backend/controllers/sandboxController.ts +63 -0
  324. package/gnoman2.0/backend/controllers/settingsController.ts +38 -0
  325. package/gnoman2.0/backend/controllers/walletController.ts +151 -0
  326. package/gnoman2.0/backend/index.ts +133 -0
  327. package/gnoman2.0/backend/licenses/license_public.pem +3 -0
  328. package/gnoman2.0/backend/licenses/verify_license.py +43 -0
  329. package/gnoman2.0/backend/routes/contractRoutes.ts +11 -0
  330. package/gnoman2.0/backend/routes/devToolsRoutes.ts +11 -0
  331. package/gnoman2.0/backend/routes/etherscanRoutes.ts +11 -0
  332. package/gnoman2.0/backend/routes/historyRoutes.ts +8 -0
  333. package/gnoman2.0/backend/routes/keyringRoutes.ts +25 -0
  334. package/gnoman2.0/backend/routes/licenseRoutes.ts +35 -0
  335. package/gnoman2.0/backend/routes/robinhoodRoutes.ts +22 -0
  336. package/gnoman2.0/backend/routes/runtimeRoutes.ts +29 -0
  337. package/gnoman2.0/backend/routes/safeRoutes.ts +28 -0
  338. package/gnoman2.0/backend/routes/sandboxRoutes.ts +17 -0
  339. package/gnoman2.0/backend/routes/settingsRoutes.ts +14 -0
  340. package/gnoman2.0/backend/routes/walletRoutes.ts +21 -0
  341. package/gnoman2.0/backend/services/chainlinkService.ts +65 -0
  342. package/gnoman2.0/backend/services/contractRegistryService.ts +205 -0
  343. package/gnoman2.0/backend/services/devToolsService.ts +251 -0
  344. package/gnoman2.0/backend/services/diagnosticsService.ts +350 -0
  345. package/gnoman2.0/backend/services/etherscanService.ts +152 -0
  346. package/gnoman2.0/backend/services/historyService.ts +89 -0
  347. package/gnoman2.0/backend/services/keyringAccessor.ts +4 -0
  348. package/gnoman2.0/backend/services/licenseService.ts +163 -0
  349. package/gnoman2.0/backend/services/onchain/abiRegistry.ts +57 -0
  350. package/gnoman2.0/backend/services/onchain/chainlinkClient.ts +56 -0
  351. package/gnoman2.0/backend/services/onchain/errors.ts +16 -0
  352. package/gnoman2.0/backend/services/onchain/etherscanClient.ts +94 -0
  353. package/gnoman2.0/backend/services/onchain/index.ts +76 -0
  354. package/gnoman2.0/backend/services/onchain/tenderlyRpcClient.ts +74 -0
  355. package/gnoman2.0/backend/services/onchain/types.ts +33 -0
  356. package/gnoman2.0/backend/services/onchainAutomationService.ts +424 -0
  357. package/gnoman2.0/backend/services/robinhood/auth.ts +42 -0
  358. package/gnoman2.0/backend/services/robinhood/client.ts +123 -0
  359. package/gnoman2.0/backend/services/robinhood/integrationService.ts +140 -0
  360. package/gnoman2.0/backend/services/robinhood/provider.ts +22 -0
  361. package/gnoman2.0/backend/services/robinhood/unofficialClient.ts +66 -0
  362. package/gnoman2.0/backend/services/rpcService.ts +44 -0
  363. package/gnoman2.0/backend/services/runtimeTelemetryService.ts +158 -0
  364. package/gnoman2.0/backend/services/safeConfigRepository.ts +205 -0
  365. package/gnoman2.0/backend/services/safeService.ts +588 -0
  366. package/gnoman2.0/backend/services/sandboxService.ts +157 -0
  367. package/gnoman2.0/backend/services/secureSettingsService.ts +45 -0
  368. package/gnoman2.0/backend/services/transactionHoldService.ts +223 -0
  369. package/gnoman2.0/backend/services/vanityService.ts +293 -0
  370. package/gnoman2.0/backend/services/walletService.ts +290 -0
  371. package/gnoman2.0/backend/services/walletStore.ts +179 -0
  372. package/gnoman2.0/backend/types/express-async-handler.d.ts +13 -0
  373. package/gnoman2.0/backend/types/keyring.d.ts +19 -0
  374. package/gnoman2.0/backend/utils/abiResolver.ts +208 -0
  375. package/gnoman2.0/backend/utils/http.ts +6 -0
  376. package/gnoman2.0/backend/utils/secretsResolver.ts +150 -0
  377. package/gnoman2.0/backend/utils/signer.ts +11 -0
  378. package/gnoman2.0/backend/workers/vanityWorker.ts +76 -0
  379. package/gnoman2.0/capacitor.config.ts +13 -0
  380. package/gnoman2.0/cli/gnoman.ts +424 -0
  381. package/gnoman2.0/contracts/OracleConsumer.sol +20 -0
  382. package/gnoman2.0/contracts/PriceFeedConsumer.sol +22 -0
  383. package/gnoman2.0/docs/development-guide.md +203 -0
  384. package/gnoman2.0/docs/etherscan-chainlink-integration.md +44 -0
  385. package/gnoman2.0/docs/gnoman-20-user-manual-STANDARD-PRINT-READY.pdf +0 -0
  386. package/gnoman2.0/docs/gnoman-20-user-manual-STANDARD.pdf +0 -0
  387. package/gnoman2.0/docs/license-dev-guide.md +106 -0
  388. package/gnoman2.0/docs/robinhood-integration.md +30 -0
  389. package/gnoman2.0/docs/system-audit-gpt-guide.md +208 -0
  390. package/gnoman2.0/docs/system-robustness-audit.md +50 -0
  391. package/gnoman2.0/docs/user-guide.md +73 -0
  392. package/gnoman2.0/docs/wiki/development-guide.md +203 -0
  393. package/gnoman2.0/docs/wiki/license-dev-guide.md +106 -0
  394. package/gnoman2.0/docs/wiki/user-guide.md +73 -0
  395. package/gnoman2.0/eslint.config.js +85 -0
  396. package/gnoman2.0/gnomon/__init__.py +0 -0
  397. package/gnoman2.0/gnomon/api/__init__.py +0 -0
  398. package/gnoman2.0/gnomon/api/etherscan_tracker.py +72 -0
  399. package/gnoman2.0/gnomon/core/__init__.py +0 -0
  400. package/gnoman2.0/gnomon/core/safe_manager.py +111 -0
  401. package/gnoman2.0/gnomon/tests/test_abi_resolver.py +181 -0
  402. package/gnoman2.0/gnomon/tests/test_safe_persistence_and_etherscan.py +97 -0
  403. package/gnoman2.0/gnomon/utils/__init__.py +5 -0
  404. package/gnoman2.0/gnomon/utils/abi_resolver.py +255 -0
  405. package/gnoman2.0/ios/ExportOptions.plist +16 -0
  406. package/gnoman2.0/ios/README.md +33 -0
  407. package/gnoman2.0/jest.config.ts +18 -0
  408. package/gnoman2.0/keyring/__init__.py +17 -0
  409. package/gnoman2.0/licensingServer/package.json +23 -0
  410. package/gnoman2.0/licensingServer/src/config/keys.ts +84 -0
  411. package/gnoman2.0/licensingServer/src/index.ts +30 -0
  412. package/gnoman2.0/licensingServer/src/lib/canonicalize.ts +5 -0
  413. package/gnoman2.0/licensingServer/src/lib/crypto.ts +25 -0
  414. package/gnoman2.0/licensingServer/src/lib/validate.ts +62 -0
  415. package/gnoman2.0/licensingServer/src/middleware/auth.ts +20 -0
  416. package/gnoman2.0/licensingServer/src/routes/licenses.ts +110 -0
  417. package/gnoman2.0/licensingServer/tsconfig.json +12 -0
  418. package/gnoman2.0/main/ipcHandlers/index.ts +23 -0
  419. package/gnoman2.0/main/keyring/keyringmanager.ts +154 -0
  420. package/gnoman2.0/main/main.ts +234 -0
  421. package/gnoman2.0/main/preload/index.ts +31 -0
  422. package/gnoman2.0/main/preload/licenseBridge.ts +73 -0
  423. package/gnoman2.0/modules/sandbox/abiLoader.ts +78 -0
  424. package/gnoman2.0/modules/sandbox/contractSimulator.ts +241 -0
  425. package/gnoman2.0/modules/sandbox/formBuilder.ts +16 -0
  426. package/gnoman2.0/modules/sandbox/index.ts +6 -0
  427. package/gnoman2.0/modules/sandbox/localFork.ts +129 -0
  428. package/gnoman2.0/modules/sandbox/safe.abi.json +82 -0
  429. package/gnoman2.0/modules/sandbox/sandboxManager.ts +154 -0
  430. package/gnoman2.0/modules/sandbox/types.ts +84 -0
  431. package/gnoman2.0/modules/sandbox/ui/LogViewer.tsx +30 -0
  432. package/gnoman2.0/modules/sandbox/ui/ParameterForm.tsx +49 -0
  433. package/gnoman2.0/modules/sandbox/ui/SandboxPanel.tsx +568 -0
  434. package/gnoman2.0/package-lock.json +10904 -0
  435. package/gnoman2.0/package.json +82 -0
  436. package/gnoman2.0/renderer/components/LicenseScreen.tsx +134 -0
  437. package/gnoman2.0/renderer/index.html +12 -0
  438. package/gnoman2.0/renderer/package-lock.json +4104 -0
  439. package/gnoman2.0/renderer/package.json +35 -0
  440. package/gnoman2.0/renderer/postcss.config.cjs +6 -0
  441. package/gnoman2.0/renderer/src/App.tsx +229 -0
  442. package/gnoman2.0/renderer/src/context/KeyringContext.tsx +217 -0
  443. package/gnoman2.0/renderer/src/context/SafeContext.tsx +49 -0
  444. package/gnoman2.0/renderer/src/context/ThemeContext.tsx +60 -0
  445. package/gnoman2.0/renderer/src/context/WalletContext.tsx +50 -0
  446. package/gnoman2.0/renderer/src/context/main.tsx +18 -0
  447. package/gnoman2.0/renderer/src/main.tsx +18 -0
  448. package/gnoman2.0/renderer/src/pages/Contracts.tsx +482 -0
  449. package/gnoman2.0/renderer/src/pages/Dashboard.tsx +653 -0
  450. package/gnoman2.0/renderer/src/pages/DeveloperTools.tsx +270 -0
  451. package/gnoman2.0/renderer/src/pages/History.tsx +149 -0
  452. package/gnoman2.0/renderer/src/pages/Keyring.tsx +449 -0
  453. package/gnoman2.0/renderer/src/pages/Safes.tsx +1089 -0
  454. package/gnoman2.0/renderer/src/pages/Sandbox.tsx +146 -0
  455. package/gnoman2.0/renderer/src/pages/Settings.tsx +871 -0
  456. package/gnoman2.0/renderer/src/pages/Wallets.tsx +752 -0
  457. package/gnoman2.0/renderer/src/pages/WikiGuide.tsx +75 -0
  458. package/gnoman2.0/renderer/src/styles.css +32 -0
  459. package/gnoman2.0/renderer/src/types/gnoman.d.ts +9 -0
  460. package/gnoman2.0/renderer/src/types/license.ts +8 -0
  461. package/gnoman2.0/renderer/src/types/safevault.d.ts +17 -0
  462. package/gnoman2.0/renderer/src/utils/backend.ts +88 -0
  463. package/gnoman2.0/renderer/tailwind.config.cjs +8 -0
  464. package/gnoman2.0/renderer/tsconfig.json +13 -0
  465. package/gnoman2.0/renderer/tsconfig.node.json +9 -0
  466. package/gnoman2.0/renderer/vite.config.ts +19 -0
  467. package/gnoman2.0/requests/__init__.py +35 -0
  468. package/gnoman2.0/scripts/build-ios.sh +30 -0
  469. package/gnoman2.0/scripts/copyBackendAssets.js +24 -0
  470. package/gnoman2.0/scripts/copyRenderer.js +87 -0
  471. package/gnoman2.0/scripts/launchElectron.js +51 -0
  472. package/gnoman2.0/src/core/backends/fileBackend.ts +154 -0
  473. package/gnoman2.0/src/core/backends/memoryBackend.ts +27 -0
  474. package/gnoman2.0/src/core/backends/systemBackend.ts +66 -0
  475. package/gnoman2.0/src/core/backends/types.ts +17 -0
  476. package/gnoman2.0/src/core/keyringManager.ts +208 -0
  477. package/gnoman2.0/src/utils/abiCache/.gitkeep +0 -0
  478. package/gnoman2.0/src/utils/abiResolver.ts +200 -0
  479. package/gnoman2.0/src/utils/runtimeObservability.ts +110 -0
  480. package/gnoman2.0/src/utils/secretsResolver.ts +144 -0
  481. package/gnoman2.0/tests/chainlinkService.test.ts +32 -0
  482. package/gnoman2.0/tests/diagnosticsService.test.ts +68 -0
  483. package/gnoman2.0/tests/etherscanController.test.ts +99 -0
  484. package/gnoman2.0/tests/etherscanService.test.ts +116 -0
  485. package/gnoman2.0/tests/keyringManager.test.ts +135 -0
  486. package/gnoman2.0/tests/onchainToolkit.test.ts +71 -0
  487. package/gnoman2.0/tests/robinhoodClient.test.ts +54 -0
  488. package/gnoman2.0/tests/robinhoodController.test.ts +81 -0
  489. package/gnoman2.0/tests/robinhoodIntegrationService.test.ts +50 -0
  490. package/gnoman2.0/tests/safeServicePersistence.test.ts +81 -0
  491. package/gnoman2.0/tests/test_contract_sandbox/sandbox.test.js +407 -0
  492. package/gnoman2.0/tests/walletController.test.ts +57 -0
  493. package/gnoman2.0/tsconfig.backend.json +7 -0
  494. package/gnoman2.0/tsconfig.cli.json +7 -0
  495. package/gnoman2.0/tsconfig.json +18 -0
  496. package/gnoman2.0/tsconfig.main.json +7 -0
  497. package/gnomon/__init__.py +0 -0
  498. package/gnomon/__pycache__/__init__.cpython-310.pyc +0 -0
  499. package/gnomon/api/__init__.py +0 -0
  500. package/gnomon/api/__pycache__/__init__.cpython-310.pyc +0 -0
  501. package/gnomon/api/__pycache__/etherscan_tracker.cpython-310.pyc +0 -0
  502. package/gnomon/api/etherscan_tracker.py +72 -0
  503. package/gnomon/core/__init__.py +0 -0
  504. package/gnomon/core/safe_manager.py +111 -0
  505. package/gnomon/tests/__pycache__/test_safe_persistence_and_etherscan.cpython-310-pytest-8.3.3.pyc +0 -0
  506. package/gnomon/tests/test_abi_resolver.py +181 -0
  507. package/gnomon/tests/test_safe_persistence_and_etherscan.py +97 -0
  508. package/gnomon/utils/__init__.py +5 -0
  509. package/gnomon/utils/abi_resolver.py +255 -0
  510. package/ios/ExportOptions.plist +16 -0
  511. package/ios/README.md +33 -0
  512. package/jest.config.ts +18 -0
  513. package/keyring/__init__.py +17 -0
  514. package/launcher.sh +57 -0
  515. package/license.env +2 -0
  516. package/licensingServer/package.json +23 -0
  517. package/licensingServer/src/config/keys.ts +84 -0
  518. package/licensingServer/src/index.ts +30 -0
  519. package/licensingServer/src/lib/canonicalize.ts +5 -0
  520. package/licensingServer/src/lib/crypto.ts +25 -0
  521. package/licensingServer/src/lib/validate.ts +62 -0
  522. package/licensingServer/src/middleware/auth.ts +20 -0
  523. package/licensingServer/src/routes/licenses.ts +110 -0
  524. package/licensingServer/tsconfig.json +12 -0
  525. package/main/ipcHandlers/index.ts +23 -0
  526. package/main/keyring/keyringmanager.ts +154 -0
  527. package/main/main.ts +234 -0
  528. package/main/preload/index.ts +31 -0
  529. package/main/preload/licenseBridge.ts +73 -0
  530. package/modules/sandbox/abiLoader.ts +78 -0
  531. package/modules/sandbox/contractSimulator.ts +241 -0
  532. package/modules/sandbox/formBuilder.ts +16 -0
  533. package/modules/sandbox/index.ts +6 -0
  534. package/modules/sandbox/localFork.ts +129 -0
  535. package/modules/sandbox/safe.abi.json +82 -0
  536. package/modules/sandbox/sandboxManager.ts +154 -0
  537. package/modules/sandbox/types.ts +84 -0
  538. package/modules/sandbox/ui/LogViewer.tsx +30 -0
  539. package/modules/sandbox/ui/ParameterForm.tsx +49 -0
  540. package/modules/sandbox/ui/SandboxPanel.tsx +568 -0
  541. package/package.json +82 -0
  542. package/renderer/components/LicenseScreen.tsx +134 -0
  543. package/renderer/index.html +12 -0
  544. package/renderer/package-lock.json +4104 -0
  545. package/renderer/package.json +35 -0
  546. package/renderer/postcss.config.cjs +6 -0
  547. package/renderer/src/App.tsx +229 -0
  548. package/renderer/src/context/KeyringContext.tsx +217 -0
  549. package/renderer/src/context/SafeContext.tsx +49 -0
  550. package/renderer/src/context/ThemeContext.tsx +60 -0
  551. package/renderer/src/context/WalletContext.tsx +50 -0
  552. package/renderer/src/context/main.tsx +18 -0
  553. package/renderer/src/main.tsx +18 -0
  554. package/renderer/src/pages/Contracts.tsx +482 -0
  555. package/renderer/src/pages/Dashboard.tsx +653 -0
  556. package/renderer/src/pages/DeveloperTools.tsx +270 -0
  557. package/renderer/src/pages/History.tsx +149 -0
  558. package/renderer/src/pages/Keyring.tsx +449 -0
  559. package/renderer/src/pages/Safes.tsx +1089 -0
  560. package/renderer/src/pages/Sandbox.tsx +146 -0
  561. package/renderer/src/pages/Settings.tsx +871 -0
  562. package/renderer/src/pages/Wallets.tsx +752 -0
  563. package/renderer/src/pages/WikiGuide.tsx +75 -0
  564. package/renderer/src/styles.css +32 -0
  565. package/renderer/src/types/gnoman.d.ts +9 -0
  566. package/renderer/src/types/license.ts +8 -0
  567. package/renderer/src/types/safevault.d.ts +17 -0
  568. package/renderer/src/utils/backend.ts +88 -0
  569. package/renderer/tailwind.config.cjs +8 -0
  570. package/renderer/tsconfig.json +13 -0
  571. package/renderer/tsconfig.node.json +9 -0
  572. package/renderer/vite.config.ts +19 -0
  573. package/requests/__init__.py +35 -0
  574. package/requests/__pycache__/__init__.cpython-310.pyc +0 -0
  575. package/scripts/build-ios.sh +30 -0
  576. package/scripts/copyBackendAssets.js +24 -0
  577. package/scripts/copyRenderer.js +87 -0
  578. package/scripts/deployBackend.sh +24 -0
  579. package/scripts/launchElectron.js +51 -0
  580. package/src/core/backends/fileBackend.ts +154 -0
  581. package/src/core/backends/memoryBackend.ts +27 -0
  582. package/src/core/backends/systemBackend.ts +66 -0
  583. package/src/core/backends/types.ts +17 -0
  584. package/src/core/keyringManager.ts +208 -0
  585. package/src/utils/abiCache/.gitkeep +0 -0
  586. package/src/utils/abiResolver.ts +200 -0
  587. package/src/utils/runtimeObservability.ts +110 -0
  588. package/src/utils/secretsResolver.ts +144 -0
  589. package/tests/chainlinkService.test.ts +32 -0
  590. package/tests/diagnosticsService.test.ts +68 -0
  591. package/tests/etherscanController.test.ts +99 -0
  592. package/tests/etherscanService.test.ts +116 -0
  593. package/tests/keyringManager.test.ts +135 -0
  594. package/tests/onchainToolkit.test.ts +71 -0
  595. package/tests/robinhoodClient.test.ts +54 -0
  596. package/tests/robinhoodController.test.ts +81 -0
  597. package/tests/robinhoodIntegrationService.test.ts +50 -0
  598. package/tests/safeServicePersistence.test.ts +81 -0
  599. package/tests/test_contract_sandbox/sandbox.test.js +407 -0
  600. package/tests/walletController.test.ts +57 -0
  601. package/touch +14 -0
  602. package/tsconfig.backend.json +7 -0
  603. package/tsconfig.cli.json +7 -0
  604. package/tsconfig.json +18 -0
  605. package/tsconfig.main.json +7 -0
  606. package/webhook-shim.js +50 -0
@@ -0,0 +1,752 @@
1
+ import { FormEvent, useCallback, useMemo, useState } from 'react';
2
+ import { useWallets } from '../context/WalletContext';
3
+ import { buildBackendUrl } from '../utils/backend';
4
+
5
+ interface WalletDetails {
6
+ address: string;
7
+ alias?: string;
8
+ hidden: boolean;
9
+ createdAt: string;
10
+ source?: string;
11
+ network?: string;
12
+ balance?: string;
13
+ publicKey?: string;
14
+ mnemonic?: string;
15
+ derivationPath?: string;
16
+ privateKey: string;
17
+ }
18
+
19
+ const Wallets = () => {
20
+ const { wallets, refresh } = useWallets();
21
+ const [loading, setLoading] = useState(false);
22
+ const [error, setError] = useState<string | undefined>();
23
+ const [propertiesOpen, setPropertiesOpen] = useState(false);
24
+ const [propertiesLoading, setPropertiesLoading] = useState(false);
25
+ const [propertiesError, setPropertiesError] = useState<string>();
26
+ const [properties, setProperties] = useState<WalletDetails | undefined>();
27
+ const [propertiesAddress, setPropertiesAddress] = useState<string>();
28
+ const [importType, setImportType] = useState<'mnemonic' | 'privateKey'>('mnemonic');
29
+ const [importLoading, setImportLoading] = useState(false);
30
+ const [importError, setImportError] = useState<string>();
31
+ const [importMessage, setImportMessage] = useState<string>();
32
+ const [txForm, setTxForm] = useState({ to: '', value: '', data: '', password: '' });
33
+ const [txLoading, setTxLoading] = useState(false);
34
+ const [txError, setTxError] = useState<string>();
35
+ const [txMessage, setTxMessage] = useState<string>();
36
+ const [removeMessage, setRemoveMessage] = useState<string>();
37
+ const [removeError, setRemoveError] = useState<string>();
38
+ const [removingAddress, setRemovingAddress] = useState<string | null>(null);
39
+
40
+ const formatRelativeTime = useCallback((value: string) => {
41
+ const timestamp = new Date(value).getTime();
42
+ if (Number.isNaN(timestamp)) {
43
+ return 'Unknown';
44
+ }
45
+ const diff = Date.now() - timestamp;
46
+ if (diff < 60 * 1000) {
47
+ return 'just now';
48
+ }
49
+ const minutes = Math.floor(diff / (60 * 1000));
50
+ if (minutes < 60) {
51
+ return `${minutes}m ago`;
52
+ }
53
+ const hours = Math.floor(minutes / 60);
54
+ if (hours < 24) {
55
+ return `${hours}h ago`;
56
+ }
57
+ const days = Math.floor(hours / 24);
58
+ if (days < 30) {
59
+ return `${days}d ago`;
60
+ }
61
+ const months = Math.floor(days / 30);
62
+ if (months < 12) {
63
+ return `${months}mo ago`;
64
+ }
65
+ const years = Math.floor(months / 12);
66
+ return `${years}y ago`;
67
+ }, []);
68
+
69
+ const inventoryMetrics = useMemo(() => {
70
+ let hidden = 0;
71
+ let aliasCount = 0;
72
+ let imported = 0;
73
+ let newest: (typeof wallets)[number] | undefined;
74
+ let oldest: (typeof wallets)[number] | undefined;
75
+ const networkCounts = new Map<string, number>();
76
+ for (const wallet of wallets) {
77
+ if (wallet.hidden) {
78
+ hidden += 1;
79
+ }
80
+ if (wallet.alias && wallet.alias.trim()) {
81
+ aliasCount += 1;
82
+ }
83
+ if (wallet.source && wallet.source !== 'generated') {
84
+ imported += 1;
85
+ }
86
+ const createdAt = new Date(wallet.createdAt).getTime();
87
+ if (!Number.isNaN(createdAt)) {
88
+ if (!newest || createdAt > new Date(newest.createdAt).getTime()) {
89
+ newest = wallet;
90
+ }
91
+ if (!oldest || createdAt < new Date(oldest.createdAt).getTime()) {
92
+ oldest = wallet;
93
+ }
94
+ }
95
+ const network = (wallet.network ?? 'mainnet').toLowerCase();
96
+ networkCounts.set(network, (networkCounts.get(network) ?? 0) + 1);
97
+ }
98
+ const total = wallets.length;
99
+ const distribution = Array.from(networkCounts.entries())
100
+ .map(([network, count]) => ({
101
+ network,
102
+ count,
103
+ percentage: total ? Math.round((count / total) * 100) : 0
104
+ }))
105
+ .sort((a, b) => b.count - a.count)
106
+ .slice(0, 5);
107
+ return { total, hidden, aliasCount, imported, newest, oldest, distribution };
108
+ }, [wallets]);
109
+
110
+ const hiddenPercentage = inventoryMetrics.total
111
+ ? Math.round((inventoryMetrics.hidden / inventoryMetrics.total) * 100)
112
+ : 0;
113
+ const aliasCoverage = inventoryMetrics.total
114
+ ? Math.round((inventoryMetrics.aliasCount / inventoryMetrics.total) * 100)
115
+ : 0;
116
+ const importedShare = inventoryMetrics.total
117
+ ? Math.round((inventoryMetrics.imported / inventoryMetrics.total) * 100)
118
+ : 0;
119
+ const newestRelative = useMemo(
120
+ () => (inventoryMetrics.newest ? formatRelativeTime(inventoryMetrics.newest.createdAt) : '—'),
121
+ [formatRelativeTime, inventoryMetrics.newest?.createdAt]
122
+ );
123
+ const oldestRelative = useMemo(
124
+ () => (inventoryMetrics.oldest ? formatRelativeTime(inventoryMetrics.oldest.createdAt) : '—'),
125
+ [formatRelativeTime, inventoryMetrics.oldest?.createdAt]
126
+ );
127
+ const walletInsights = useMemo(() => {
128
+ const insights: string[] = [];
129
+ if (!inventoryMetrics.total) {
130
+ insights.push('No wallets stored yet. Generate a wallet to populate telemetry.');
131
+ return insights;
132
+ }
133
+ insights.push(
134
+ `${hiddenPercentage}% hidden via keyring isolation (${inventoryMetrics.hidden}/${inventoryMetrics.total}).`
135
+ );
136
+ insights.push(
137
+ aliasCoverage
138
+ ? `${inventoryMetrics.aliasCount} wallet aliases assigned for quick operator recognition.`
139
+ : 'Add wallet aliases to improve operator visibility during incident response.'
140
+ );
141
+ insights.push(
142
+ importedShare
143
+ ? `${inventoryMetrics.imported} imported wallets (${importedShare}%).`
144
+ : 'All managed wallets were generated locally inside GNOMAN.'
145
+ );
146
+ if (inventoryMetrics.newest) {
147
+ insights.push(`Most recent rotation happened ${newestRelative}.`);
148
+ }
149
+ if (inventoryMetrics.oldest) {
150
+ insights.push(`Longest-lived wallet observed ${oldestRelative}.`);
151
+ }
152
+ return insights;
153
+ }, [aliasCoverage, hiddenPercentage, importedShare, inventoryMetrics, newestRelative, oldestRelative]);
154
+
155
+ const handleGenerate = async (event: FormEvent<HTMLFormElement>) => {
156
+ event.preventDefault();
157
+ const formData = new FormData(event.currentTarget);
158
+ setLoading(true);
159
+ setError(undefined);
160
+ try {
161
+ const response = await fetch(buildBackendUrl('/api/wallets/generate'), {
162
+ method: 'POST',
163
+ headers: { 'Content-Type': 'application/json' },
164
+ body: JSON.stringify({
165
+ alias: formData.get('alias') || undefined,
166
+ password: formData.get('password') || undefined,
167
+ hidden: formData.get('hidden') === 'on'
168
+ })
169
+ });
170
+ if (!response.ok) {
171
+ throw new Error('Wallet generation failed');
172
+ }
173
+ await refresh();
174
+ event.currentTarget.reset();
175
+ } catch (err) {
176
+ setError(err instanceof Error ? err.message : 'Failed to create wallet');
177
+ } finally {
178
+ setLoading(false);
179
+ }
180
+ };
181
+
182
+ const handleImport = async (event: FormEvent<HTMLFormElement>) => {
183
+ event.preventDefault();
184
+ const formData = new FormData(event.currentTarget);
185
+ setImportLoading(true);
186
+ setImportError(undefined);
187
+ setImportMessage(undefined);
188
+ const endpoint =
189
+ importType === 'mnemonic' ? '/api/wallets/import/mnemonic' : '/api/wallets/import/private-key';
190
+ const payload =
191
+ importType === 'mnemonic'
192
+ ? {
193
+ mnemonic: formData.get('mnemonic'),
194
+ path: formData.get('derivationPath') || undefined,
195
+ alias: formData.get('importAlias') || undefined,
196
+ password: formData.get('importPassword') || undefined,
197
+ hidden: formData.get('importHidden') === 'on'
198
+ }
199
+ : {
200
+ privateKey: formData.get('privateKey'),
201
+ alias: formData.get('importAlias') || undefined,
202
+ password: formData.get('importPassword') || undefined,
203
+ hidden: formData.get('importHidden') === 'on'
204
+ };
205
+ try {
206
+ const response = await fetch(buildBackendUrl(endpoint), {
207
+ method: 'POST',
208
+ headers: { 'Content-Type': 'application/json' },
209
+ body: JSON.stringify(payload)
210
+ });
211
+ if (!response.ok) {
212
+ throw new Error('Wallet import failed');
213
+ }
214
+ await refresh();
215
+ setImportMessage('Wallet imported');
216
+ event.currentTarget.reset();
217
+ } catch (err) {
218
+ setImportError(err instanceof Error ? err.message : 'Failed to import wallet');
219
+ } finally {
220
+ setImportLoading(false);
221
+ }
222
+ };
223
+
224
+ const safeRefresh = async () => {
225
+ try {
226
+ await refresh();
227
+ } catch (err) {
228
+ setError(err instanceof Error ? err.message : 'Failed to refresh wallets');
229
+ }
230
+ };
231
+
232
+ const openProperties = async (address: string) => {
233
+ setPropertiesAddress(address);
234
+ setPropertiesOpen(true);
235
+ setPropertiesLoading(true);
236
+ setPropertiesError(undefined);
237
+ setProperties(undefined);
238
+ setTxForm({ to: '', value: '', data: '', password: '' });
239
+ setTxError(undefined);
240
+ setTxMessage(undefined);
241
+ try {
242
+ const response = await fetch(buildBackendUrl(`/api/wallets/${address}/details`));
243
+ if (!response.ok) {
244
+ throw new Error('Unable to load wallet details');
245
+ }
246
+ const payload = (await response.json()) as WalletDetails;
247
+ setProperties(payload);
248
+ } catch (err) {
249
+ setPropertiesError(err instanceof Error ? err.message : 'Failed to fetch wallet details');
250
+ } finally {
251
+ setPropertiesLoading(false);
252
+ }
253
+ };
254
+
255
+ const closeProperties = () => {
256
+ setPropertiesOpen(false);
257
+ setProperties(undefined);
258
+ setPropertiesAddress(undefined);
259
+ setPropertiesError(undefined);
260
+ setTxError(undefined);
261
+ setTxMessage(undefined);
262
+ };
263
+
264
+ const derivedBalance = useMemo(() => {
265
+ if (!properties?.balance) {
266
+ return 'Not yet synced';
267
+ }
268
+ return properties.balance.includes('ETH') ? properties.balance : `${properties.balance} ETH`;
269
+ }, [properties?.balance]);
270
+
271
+ const mnemonicLabel = useMemo(() => {
272
+ if (!properties) {
273
+ return '—';
274
+ }
275
+ if (properties.source === 'privateKey') {
276
+ return 'Not available (imported via private key)';
277
+ }
278
+ return properties.mnemonic ?? 'Unavailable';
279
+ }, [properties]);
280
+
281
+ const derivationPathLabel = useMemo(() => {
282
+ if (!properties) {
283
+ return '—';
284
+ }
285
+ if (properties.source === 'privateKey') {
286
+ return 'Not applicable for private key imports';
287
+ }
288
+ return properties.derivationPath ?? '—';
289
+ }, [properties]);
290
+
291
+ const handleSendTransaction = async (event: FormEvent<HTMLFormElement>) => {
292
+ event.preventDefault();
293
+ if (!propertiesAddress) {
294
+ return;
295
+ }
296
+ setTxLoading(true);
297
+ setTxError(undefined);
298
+ setTxMessage(undefined);
299
+ try {
300
+ const response = await fetch(buildBackendUrl(`/api/wallets/${propertiesAddress}/transactions`), {
301
+ method: 'POST',
302
+ headers: { 'Content-Type': 'application/json' },
303
+ body: JSON.stringify({
304
+ to: txForm.to,
305
+ value: txForm.value || undefined,
306
+ data: txForm.data || undefined,
307
+ password: txForm.password
308
+ })
309
+ });
310
+ if (!response.ok) {
311
+ throw new Error('Failed to send transaction');
312
+ }
313
+ const payload = (await response.json()) as { hash: string };
314
+ setTxMessage(`Transaction submitted: ${payload.hash}`);
315
+ setTxForm({ to: '', value: '', data: '', password: '' });
316
+ } catch (err) {
317
+ setTxError(err instanceof Error ? err.message : 'Unable to send transaction');
318
+ } finally {
319
+ setTxLoading(false);
320
+ }
321
+ };
322
+
323
+ const handleRemoveWallet = async (address: string, alias?: string) => {
324
+ const label = alias?.trim() ? `${alias} (${address})` : address;
325
+ const confirmed = window.confirm(`Remove wallet ${label}? This cannot be undone.`);
326
+ if (!confirmed) {
327
+ return;
328
+ }
329
+ setRemoveMessage(undefined);
330
+ setRemoveError(undefined);
331
+ setRemovingAddress(address);
332
+ try {
333
+ const response = await fetch(buildBackendUrl(`/api/wallets/${address}`), { method: 'DELETE' });
334
+ if (!response.ok) {
335
+ throw new Error('Failed to remove wallet');
336
+ }
337
+ await refresh();
338
+ setRemoveMessage('Wallet removed.');
339
+ if (propertiesAddress === address) {
340
+ closeProperties();
341
+ }
342
+ } catch (err) {
343
+ setRemoveError(err instanceof Error ? err.message : 'Unable to remove wallet');
344
+ } finally {
345
+ setRemovingAddress(null);
346
+ }
347
+ };
348
+
349
+ return (
350
+ <div className="space-y-6">
351
+ <section className="rounded-2xl border border-slate-800 bg-slate-900/60 p-6">
352
+ <div className="flex items-center justify-between">
353
+ <h2 className="text-sm font-semibold text-slate-200">Inventory overview</h2>
354
+ <span className="text-xs uppercase tracking-widest text-slate-500">
355
+ {inventoryMetrics.total ? 'Keyring sync active' : 'Awaiting wallets'}
356
+ </span>
357
+ </div>
358
+ <div className="mt-4 grid gap-4 sm:grid-cols-3">
359
+ <div>
360
+ <p className="text-xs uppercase tracking-widest text-slate-500">Managed wallets</p>
361
+ <p className="mt-2 text-2xl font-semibold text-white">{inventoryMetrics.total}</p>
362
+ <p className="mt-1 text-xs text-slate-400">
363
+ {inventoryMetrics.total
364
+ ? `${inventoryMetrics.hidden} hidden • ${inventoryMetrics.total - inventoryMetrics.hidden} visible`
365
+ : 'Generate or import to begin'}
366
+ </p>
367
+ </div>
368
+ <div>
369
+ <p className="text-xs uppercase tracking-widest text-slate-500">Hidden coverage</p>
370
+ <p className="mt-2 text-2xl font-semibold text-white">{hiddenPercentage}%</p>
371
+ <p className="mt-1 text-xs text-slate-400">
372
+ {inventoryMetrics.hidden
373
+ ? 'Keyring isolation enforced'
374
+ : 'No hidden wallets stored'}
375
+ </p>
376
+ </div>
377
+ <div>
378
+ <p className="text-xs uppercase tracking-widest text-slate-500">Rotation cadence</p>
379
+ <p className="mt-2 text-2xl font-semibold text-white">{newestRelative}</p>
380
+ <p className="mt-1 text-xs text-slate-400">Longest lived: {oldestRelative}</p>
381
+ </div>
382
+ </div>
383
+ <div className="mt-6 grid gap-4 md:grid-cols-2">
384
+ <div className="rounded-2xl border border-slate-800/80 bg-slate-950/60 p-4">
385
+ <p className="text-xs uppercase tracking-widest text-slate-500">Network distribution</p>
386
+ <ul className="mt-3 space-y-2 text-xs text-slate-300">
387
+ {inventoryMetrics.distribution.length ? (
388
+ inventoryMetrics.distribution.map((entry) => (
389
+ <li
390
+ key={entry.network}
391
+ className="flex items-center justify-between rounded-lg border border-slate-800/80 bg-slate-900/60 px-3 py-2"
392
+ >
393
+ <span className="font-medium capitalize text-white">{entry.network}</span>
394
+ <span className="text-[11px] text-slate-400">
395
+ {entry.count} • {entry.percentage}%
396
+ </span>
397
+ </li>
398
+ ))
399
+ ) : (
400
+ <li className="rounded-lg border border-dashed border-slate-700 bg-slate-950/40 px-3 py-2 text-slate-500">
401
+ Distribution will appear once wallets are generated.
402
+ </li>
403
+ )}
404
+ </ul>
405
+ </div>
406
+ <div className="rounded-2xl border border-slate-800/80 bg-slate-950/60 p-4">
407
+ <p className="text-xs uppercase tracking-widest text-slate-500">Operational insights</p>
408
+ <ul className="mt-3 space-y-2 text-xs text-slate-300">
409
+ {walletInsights.map((insight) => (
410
+ <li key={insight} className="rounded-lg border border-slate-800/80 bg-slate-900/60 px-3 py-2">
411
+ {insight}
412
+ </li>
413
+ ))}
414
+ </ul>
415
+ </div>
416
+ </div>
417
+ </section>
418
+
419
+ <div className="grid gap-6 md:grid-cols-[2fr,3fr]">
420
+ <div className="space-y-6">
421
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
422
+ <h2 className="text-lg font-semibold">Generate Wallet</h2>
423
+ <p className="mt-1 text-sm text-slate-500">
424
+ Create a new wallet secured with encryption. Hidden wallets are stored only in the active keyring service.
425
+ </p>
426
+ <form className="mt-4 space-y-3" onSubmit={handleGenerate}>
427
+ <label className="block text-sm">
428
+ <span className="text-slate-300">Alias</span>
429
+ <input name="alias" className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2" />
430
+ </label>
431
+ <label className="block text-sm">
432
+ <span className="text-slate-300">Encryption Password</span>
433
+ <input
434
+ name="password"
435
+ type="password"
436
+ placeholder="Auto-generate when empty"
437
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2"
438
+ />
439
+ </label>
440
+ <label className="flex items-center gap-2 text-sm text-slate-300">
441
+ <input type="checkbox" name="hidden" className="h-4 w-4 rounded border-slate-700" />
442
+ Hidden wallet (keyring storage)
443
+ </label>
444
+ <button
445
+ type="submit"
446
+ disabled={loading}
447
+ className="w-full rounded bg-emerald-500 px-4 py-2 text-sm font-semibold text-emerald-950 transition hover:bg-emerald-400 disabled:opacity-50"
448
+ >
449
+ {loading ? 'Generating...' : 'Generate Wallet'}
450
+ </button>
451
+ {error && <p className="text-sm text-red-400">{error}</p>}
452
+ </form>
453
+ </section>
454
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
455
+ <h2 className="text-lg font-semibold">Import Wallet</h2>
456
+ <p className="mt-1 text-sm text-slate-500">
457
+ Bring an existing wallet into GNOMAN using a mnemonic phrase or private key.
458
+ </p>
459
+ <div className="mt-4 flex gap-2 rounded-lg border border-slate-800 bg-slate-950/60 p-1 text-xs">
460
+ <button
461
+ type="button"
462
+ onClick={() => setImportType('mnemonic')}
463
+ className={`flex-1 rounded px-3 py-2 font-semibold transition ${
464
+ importType === 'mnemonic'
465
+ ? 'bg-emerald-500 text-emerald-950'
466
+ : 'text-slate-300 hover:text-white'
467
+ }`}
468
+ >
469
+ Mnemonic
470
+ </button>
471
+ <button
472
+ type="button"
473
+ onClick={() => setImportType('privateKey')}
474
+ className={`flex-1 rounded px-3 py-2 font-semibold transition ${
475
+ importType === 'privateKey'
476
+ ? 'bg-purple-500 text-purple-950'
477
+ : 'text-slate-300 hover:text-white'
478
+ }`}
479
+ >
480
+ Private key
481
+ </button>
482
+ </div>
483
+ <form className="mt-4 space-y-3" onSubmit={handleImport}>
484
+ {importType === 'mnemonic' ? (
485
+ <>
486
+ <label className="block text-sm">
487
+ <span className="text-slate-300">Mnemonic phrase</span>
488
+ <textarea
489
+ name="mnemonic"
490
+ rows={3}
491
+ required
492
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
493
+ placeholder="word word word ..."
494
+ />
495
+ </label>
496
+ <label className="block text-sm">
497
+ <span className="text-slate-300">Derivation path</span>
498
+ <input
499
+ name="derivationPath"
500
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
501
+ placeholder="m/44'/60'/0'/0/0"
502
+ />
503
+ </label>
504
+ </>
505
+ ) : (
506
+ <label className="block text-sm">
507
+ <span className="text-slate-300">Private key</span>
508
+ <input
509
+ name="privateKey"
510
+ required
511
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
512
+ placeholder="0x..."
513
+ />
514
+ </label>
515
+ )}
516
+ <label className="block text-sm">
517
+ <span className="text-slate-300">Alias</span>
518
+ <input
519
+ name="importAlias"
520
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
521
+ />
522
+ </label>
523
+ <label className="block text-sm">
524
+ <span className="text-slate-300">Encryption Password</span>
525
+ <input
526
+ name="importPassword"
527
+ type="password"
528
+ placeholder="Auto-generate when empty"
529
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
530
+ />
531
+ </label>
532
+ <label className="flex items-center gap-2 text-sm text-slate-300">
533
+ <input type="checkbox" name="importHidden" className="h-4 w-4 rounded border-slate-700" />
534
+ Hidden wallet (keyring storage)
535
+ </label>
536
+ <button
537
+ type="submit"
538
+ disabled={importLoading}
539
+ className="w-full rounded bg-emerald-500 px-4 py-2 text-sm font-semibold text-emerald-950 transition hover:bg-emerald-400 disabled:opacity-50"
540
+ >
541
+ {importLoading ? 'Importing...' : 'Import wallet'}
542
+ </button>
543
+ {importError && <p className="text-sm text-red-400">{importError}</p>}
544
+ {importMessage && <p className="text-sm text-emerald-300">{importMessage}</p>}
545
+ </form>
546
+ </section>
547
+ </div>
548
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
549
+ <div className="flex items-center justify-between">
550
+ <h2 className="text-lg font-semibold">Stored Wallets</h2>
551
+ <button
552
+ onClick={() => safeRefresh()}
553
+ className="rounded border border-slate-700 px-3 py-1 text-xs text-slate-300 transition hover:bg-slate-800"
554
+ >
555
+ Refresh
556
+ </button>
557
+ </div>
558
+ {(removeMessage || removeError) && (
559
+ <p className={`mt-2 text-xs ${removeError ? 'text-red-400' : 'text-emerald-300'}`}>
560
+ {removeError ?? removeMessage}
561
+ </p>
562
+ )}
563
+ <ul className="mt-4 space-y-3">
564
+ {wallets.map((wallet) => (
565
+ <li key={wallet.address} className="rounded border border-slate-800 p-3">
566
+ <p className="font-mono text-sm text-emerald-300">{wallet.address}</p>
567
+ <p className="text-xs text-slate-500">
568
+ Alias: {wallet.alias ?? '—'} • Created {new Date(wallet.createdAt).toLocaleString()} • Source: {wallet.source ?? 'generated'} • Network: {wallet.network ?? 'mainnet'} • Balance:{' '}
569
+ {wallet.balance
570
+ ? wallet.balance.includes('ETH')
571
+ ? wallet.balance
572
+ : `${wallet.balance} ETH`
573
+ : 'Not yet synced'}
574
+ </p>
575
+ <div className="mt-2 flex flex-wrap gap-2">
576
+ <span
577
+ className={`rounded-full px-2 py-0.5 text-[11px] ${
578
+ wallet.hidden
579
+ ? 'bg-slate-800 text-slate-300'
580
+ : 'bg-emerald-500/10 text-emerald-300'
581
+ }`}
582
+ >
583
+ {wallet.hidden ? 'Hidden' : 'Visible'}
584
+ </span>
585
+ <button
586
+ onClick={() => openProperties(wallet.address)}
587
+ className="rounded border border-slate-700 px-2 py-0.5 text-[11px] font-semibold text-slate-200 transition hover:bg-slate-800"
588
+ >
589
+ View properties
590
+ </button>
591
+ <button
592
+ onClick={() => handleRemoveWallet(wallet.address, wallet.alias)}
593
+ className="rounded border border-red-500/40 px-2 py-0.5 text-[11px] font-semibold text-red-300 transition hover:bg-red-500/10 disabled:opacity-50"
594
+ disabled={removingAddress === wallet.address}
595
+ >
596
+ {removingAddress === wallet.address ? 'Removing…' : 'Remove'}
597
+ </button>
598
+ </div>
599
+ </li>
600
+ ))}
601
+ {wallets.length === 0 && (
602
+ <li className="rounded border border-dashed border-slate-700 p-4 text-sm text-slate-500">
603
+ No wallets stored yet.
604
+ </li>
605
+ )}
606
+ </ul>
607
+ </section>
608
+ </div>
609
+
610
+ {propertiesOpen && (
611
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/80 p-4">
612
+ <div className="relative w-full max-w-2xl rounded-xl border border-slate-800 bg-slate-900 p-6 shadow-xl">
613
+ <button
614
+ onClick={closeProperties}
615
+ className="absolute right-4 top-4 rounded-full border border-slate-700 px-2 py-0.5 text-xs text-slate-300 transition hover:bg-slate-800"
616
+ >
617
+ Close
618
+ </button>
619
+ <h3 className="text-lg font-semibold text-white">Wallet properties</h3>
620
+ <p className="mt-1 text-xs text-slate-400">{propertiesAddress}</p>
621
+ <div className="mt-4 space-y-3 text-sm text-slate-300">
622
+ {propertiesLoading && <p className="text-slate-400">Loading properties…</p>}
623
+ {propertiesError && <p className="text-red-400">{propertiesError}</p>}
624
+ {!propertiesLoading && !propertiesError && properties && (
625
+ <div className="space-y-4">
626
+ <div className="grid gap-3 sm:grid-cols-2">
627
+ <div>
628
+ <p className="text-xs uppercase tracking-widest text-slate-500">Alias</p>
629
+ <p className="font-medium text-white">{properties.alias ?? '—'}</p>
630
+ </div>
631
+ <div>
632
+ <p className="text-xs uppercase tracking-widest text-slate-500">Created</p>
633
+ <p>{new Date(properties.createdAt).toLocaleString()}</p>
634
+ </div>
635
+ <div>
636
+ <p className="text-xs uppercase tracking-widest text-slate-500">Network</p>
637
+ <p>{properties.network ?? 'mainnet'}</p>
638
+ </div>
639
+ <div>
640
+ <p className="text-xs uppercase tracking-widest text-slate-500">Balance</p>
641
+ <p>{derivedBalance}</p>
642
+ </div>
643
+ <div>
644
+ <p className="text-xs uppercase tracking-widest text-slate-500">Visibility</p>
645
+ <p>{properties.hidden ? 'Hidden (keyring storage)' : 'Visible'}</p>
646
+ </div>
647
+ <div>
648
+ <p className="text-xs uppercase tracking-widest text-slate-500">Source</p>
649
+ <p>{properties.source ?? 'generated'}</p>
650
+ </div>
651
+ </div>
652
+ <div className="space-y-3 rounded-lg border border-slate-800 bg-slate-950/60 p-4">
653
+ <div>
654
+ <p className="text-xs uppercase tracking-widest text-slate-500">Public key</p>
655
+ <p className="mt-1 break-all font-mono text-xs text-emerald-300">
656
+ {properties.publicKey ?? 'Unavailable'}
657
+ </p>
658
+ </div>
659
+ <div>
660
+ <p className="text-xs uppercase tracking-widest text-slate-500">Private key</p>
661
+ <p className="mt-1 break-all font-mono text-xs text-amber-300">
662
+ {properties.privateKey}
663
+ </p>
664
+ </div>
665
+ <div>
666
+ <p className="text-xs uppercase tracking-widest text-slate-500">Mnemonic</p>
667
+ <p className="mt-1 break-words font-mono text-xs text-slate-200">
668
+ {mnemonicLabel}
669
+ </p>
670
+ </div>
671
+ <div>
672
+ <p className="text-xs uppercase tracking-widest text-slate-500">Derivation path</p>
673
+ <p className="mt-1 font-mono text-xs text-slate-200">
674
+ {derivationPathLabel}
675
+ </p>
676
+ </div>
677
+ </div>
678
+ <div className="space-y-3 rounded-lg border border-slate-800 bg-slate-950/60 p-4">
679
+ <h4 className="text-sm font-semibold text-white">Send transaction</h4>
680
+ <form className="space-y-3" onSubmit={handleSendTransaction}>
681
+ <label className="block text-xs uppercase tracking-widest text-slate-500">To address</label>
682
+ <input
683
+ value={txForm.to}
684
+ onChange={(event) => setTxForm((prev) => ({ ...prev, to: event.target.value }))}
685
+ className="w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
686
+ placeholder="0x..."
687
+ required
688
+ />
689
+ <label className="block text-xs uppercase tracking-widest text-slate-500">Value (ETH)</label>
690
+ <input
691
+ value={txForm.value}
692
+ onChange={(event) => setTxForm((prev) => ({ ...prev, value: event.target.value }))}
693
+ className="w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
694
+ placeholder="0.0"
695
+ />
696
+ <label className="block text-xs uppercase tracking-widest text-slate-500">Data (optional)</label>
697
+ <textarea
698
+ value={txForm.data}
699
+ onChange={(event) => setTxForm((prev) => ({ ...prev, data: event.target.value }))}
700
+ className="w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
701
+ rows={3}
702
+ placeholder="0x"
703
+ />
704
+ <label className="block text-xs uppercase tracking-widest text-slate-500">Wallet password</label>
705
+ <input
706
+ type="password"
707
+ value={txForm.password}
708
+ onChange={(event) => setTxForm((prev) => ({ ...prev, password: event.target.value }))}
709
+ className="w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
710
+ placeholder="Password used to encrypt the wallet"
711
+ required
712
+ />
713
+ <button
714
+ type="submit"
715
+ disabled={txLoading}
716
+ className="w-full rounded bg-blue-500 px-3 py-2 text-xs font-semibold text-blue-950 transition hover:bg-blue-400 disabled:opacity-50"
717
+ >
718
+ {txLoading ? 'Sending...' : 'Send transaction'}
719
+ </button>
720
+ {(txError || txMessage) && (
721
+ <p className={`text-xs ${txError ? 'text-red-400' : 'text-emerald-400'}`}>
722
+ {txError ?? txMessage}
723
+ </p>
724
+ )}
725
+ </form>
726
+ </div>
727
+ <div className="space-y-2 rounded-lg border border-red-500/30 bg-red-500/5 p-4 text-xs text-red-200">
728
+ <p className="text-sm font-semibold text-red-100">Remove wallet</p>
729
+ <p>
730
+ This permanently deletes the wallet record from local storage. Export the wallet first if you
731
+ still need the private key.
732
+ </p>
733
+ <button
734
+ type="button"
735
+ onClick={() => handleRemoveWallet(properties.address, properties.alias)}
736
+ className="inline-flex items-center gap-2 rounded border border-red-500/50 px-3 py-1 text-xs font-semibold text-red-200 transition hover:bg-red-500/10 disabled:opacity-50"
737
+ disabled={removingAddress === properties.address}
738
+ >
739
+ {removingAddress === properties.address ? 'Removing…' : 'Remove wallet'}
740
+ </button>
741
+ </div>
742
+ </div>
743
+ )}
744
+ </div>
745
+ </div>
746
+ </div>
747
+ )}
748
+ </div>
749
+ );
750
+ };
751
+
752
+ export default Wallets;