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,871 @@
1
+ import { FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { LicenseStatus } from '../types/license';
4
+ import {
5
+ buildBackendUrl,
6
+ detectBackendBaseUrl,
7
+ getBackendBaseUrl,
8
+ probeBackend,
9
+ setBackendBaseUrl
10
+ } from '../utils/backend';
11
+
12
+ type VanityJobStatus = 'running' | 'completed' | 'cancelled' | 'failed';
13
+
14
+ interface RobinhoodCredentialStatus {
15
+ configured: boolean;
16
+ apiKeyPreview?: string;
17
+ enabled?: boolean;
18
+ mode?: string;
19
+ auth?: { ok: boolean; reason?: string };
20
+ }
21
+
22
+
23
+ interface RuntimeCapabilitiesSnapshot {
24
+ safe: { enabled: boolean; reason: string };
25
+ etherscan: { enabled: boolean; reason: string };
26
+ robinhood: { enabled: boolean; reason: string };
27
+ }
28
+
29
+ interface RuntimeTelemetrySnapshot {
30
+ secrets: Array<{ key: string; present: boolean; source: string }>;
31
+ abi: { cacheHits: number; cacheMisses: number; lastResolves: Array<{ address: string; contractName: string; source: string; cached: boolean; chainId: number }> };
32
+ safe: { version?: string; mastercopyAddress?: string; moduleEnabled?: boolean };
33
+ robinhood: { enabled: boolean; auth: { ok: boolean; reason?: string }; requests: Array<{ endpoint: string; statusCode: number; latencyMs: number }>; orders: Array<{ action: string; id: string; state?: string }> };
34
+ }
35
+
36
+ interface VanityJobSummary {
37
+ id: string;
38
+ status: VanityJobStatus;
39
+ attempts: number;
40
+ attemptRate?: number;
41
+ targetAttempts?: number;
42
+ startedAt: string;
43
+ completedAt?: string;
44
+ address?: string;
45
+ etaMs?: number;
46
+ label?: string;
47
+ pattern: {
48
+ prefix?: string;
49
+ suffix?: string;
50
+ regex?: string;
51
+ derivationPath?: string;
52
+ };
53
+ message?: string;
54
+ mnemonicAlias?: string;
55
+ updatedAt?: string;
56
+ }
57
+
58
+ const Settings = () => {
59
+ const [status, setStatus] = useState<LicenseStatus>({ active: false });
60
+ const [licenseToken, setLicenseToken] = useState('');
61
+ const [loading, setLoading] = useState(false);
62
+ const [error, setError] = useState('');
63
+ const [success, setSuccess] = useState('');
64
+ const [holdEnabled, setHoldEnabled] = useState(true);
65
+ const [holdHours, setHoldHours] = useState(24);
66
+ const [holdSaving, setHoldSaving] = useState(false);
67
+ const [holdMessage, setHoldMessage] = useState('');
68
+ const [vanityJobs, setVanityJobs] = useState<VanityJobSummary[]>([]);
69
+ const [vanityLoading, setVanityLoading] = useState(false);
70
+ const [vanityMessage, setVanityMessage] = useState('');
71
+ const [vanitySubmitting, setVanitySubmitting] = useState(false);
72
+ const [backendUrl, setBackendUrl] = useState(() => getBackendBaseUrl());
73
+ const [backendMessage, setBackendMessage] = useState('');
74
+ const [backendChecking, setBackendChecking] = useState(false);
75
+ const [robinhoodStatus, setRobinhoodStatus] = useState<RobinhoodCredentialStatus>({ configured: false });
76
+ const [robinhoodApiKey, setRobinhoodApiKey] = useState('');
77
+ const [robinhoodPrivateKey, setRobinhoodPrivateKey] = useState('');
78
+ const [robinhoodSymbol, setRobinhoodSymbol] = useState('BTC-USD');
79
+ const [robinhoodCashAmount, setRobinhoodCashAmount] = useState('25');
80
+ const [robinhoodMessage, setRobinhoodMessage] = useState('');
81
+ const [robinhoodOrderId, setRobinhoodOrderId] = useState('');
82
+ const [robinhoodLoading, setRobinhoodLoading] = useState(false);
83
+ const [runtimeTelemetry, setRuntimeTelemetry] = useState<RuntimeTelemetrySnapshot | null>(null);
84
+ const [runtimeCapabilities, setRuntimeCapabilities] = useState<RuntimeCapabilitiesSnapshot | null>(null);
85
+ const [vanityForm, setVanityForm] = useState({
86
+ prefix: '',
87
+ suffix: '',
88
+ regex: '',
89
+ derivationPath: "m/44'/60'/0'/0/0",
90
+ label: '',
91
+ maxAttempts: 0
92
+ });
93
+
94
+ useEffect(() => {
95
+ const fetchStatus = async () => {
96
+ try {
97
+ const response = await fetch(buildBackendUrl('/api/license'));
98
+ if (!response.ok) {
99
+ throw new Error('Unable to load license status.');
100
+ }
101
+ const data: LicenseStatus = await response.json();
102
+ setStatus(data);
103
+ } catch (err) {
104
+ console.error(err);
105
+ }
106
+ };
107
+
108
+ const fetchHoldSettings = async () => {
109
+ try {
110
+ const response = await fetch(buildBackendUrl('/api/settings/transaction-hold'));
111
+ if (!response.ok) {
112
+ throw new Error('Unable to load transaction hold settings.');
113
+ }
114
+ const data: { enabled: boolean; holdHours: number } = await response.json();
115
+ setHoldEnabled(data.enabled);
116
+ setHoldHours(data.holdHours);
117
+ } catch (err) {
118
+ console.error(err);
119
+ }
120
+ };
121
+
122
+
123
+ const fetchRobinhoodStatus = async () => {
124
+ try {
125
+ const response = await fetch(buildBackendUrl('/api/brokers/robinhood/crypto/credentials'));
126
+ if (!response.ok) {
127
+ throw new Error('Unable to load Robinhood credentials.');
128
+ }
129
+ const data: RobinhoodCredentialStatus = await response.json();
130
+ setRobinhoodStatus(data);
131
+ } catch (err) {
132
+ console.error(err);
133
+ }
134
+ };
135
+
136
+ const fetchRuntimeTelemetry = async () => {
137
+ try {
138
+ const response = await fetch(buildBackendUrl('/api/runtime/telemetry'));
139
+ if (!response.ok) {
140
+ throw new Error('Unable to load runtime telemetry.');
141
+ }
142
+ const data: RuntimeTelemetrySnapshot = await response.json();
143
+ setRuntimeTelemetry(data);
144
+ } catch (err) {
145
+ console.error(err);
146
+ }
147
+ };
148
+
149
+ const fetchRuntimeCapabilities = async () => {
150
+ try {
151
+ const response = await fetch(buildBackendUrl('/api/runtime/capabilities'));
152
+ if (!response.ok) {
153
+ throw new Error('Unable to load runtime capabilities.');
154
+ }
155
+ const data: RuntimeCapabilitiesSnapshot = await response.json();
156
+ setRuntimeCapabilities(data);
157
+ } catch (err) {
158
+ console.error(err);
159
+ }
160
+ };
161
+
162
+ void fetchStatus();
163
+ void fetchHoldSettings();
164
+ void fetchRobinhoodStatus();
165
+ void fetchRuntimeTelemetry();
166
+ void fetchRuntimeCapabilities();
167
+ }, []);
168
+
169
+ const refreshVanityJobs = useCallback(async () => {
170
+ try {
171
+ setVanityLoading(true);
172
+ const response = await fetch(buildBackendUrl('/api/wallets/vanity'));
173
+ if (!response.ok) {
174
+ throw new Error('Unable to load vanity jobs');
175
+ }
176
+ const payload = (await response.json()) as VanityJobSummary[];
177
+ setVanityJobs(Array.isArray(payload) ? payload : []);
178
+ } catch (err) {
179
+ console.error(err);
180
+ } finally {
181
+ setVanityLoading(false);
182
+ }
183
+ }, []);
184
+
185
+ useEffect(() => {
186
+ void refreshVanityJobs();
187
+ const interval = window.setInterval(() => {
188
+ void refreshVanityJobs();
189
+ }, 5000);
190
+ return () => {
191
+ window.clearInterval(interval);
192
+ };
193
+ }, [refreshVanityJobs]);
194
+
195
+ const handleBackendSave = async (event: FormEvent<HTMLFormElement>) => {
196
+ event.preventDefault();
197
+ setBackendMessage('');
198
+ const trimmed = backendUrl.trim();
199
+ if (!trimmed) {
200
+ setBackendMessage('Enter a backend URL to continue.');
201
+ return;
202
+ }
203
+ setBackendChecking(true);
204
+ const healthy = await probeBackend(trimmed);
205
+ if (!healthy) {
206
+ setBackendMessage('Unable to reach the backend health endpoint.');
207
+ setBackendChecking(false);
208
+ return;
209
+ }
210
+ const normalized = setBackendBaseUrl(trimmed);
211
+ setBackendUrl(normalized);
212
+ setBackendMessage(`Connected to ${normalized}.`);
213
+ setBackendChecking(false);
214
+ };
215
+
216
+ const handleBackendDetect = async () => {
217
+ setBackendMessage('');
218
+ setBackendChecking(true);
219
+ const detected = await detectBackendBaseUrl();
220
+ if (!detected) {
221
+ setBackendMessage('No reachable backend detected. Check the host and port.');
222
+ setBackendChecking(false);
223
+ return;
224
+ }
225
+ const normalized = setBackendBaseUrl(detected);
226
+ setBackendUrl(normalized);
227
+ setBackendMessage(`Auto-detected ${normalized}.`);
228
+ setBackendChecking(false);
229
+ };
230
+
231
+ const handleRobinhoodCredentialsSave = async (event: FormEvent<HTMLFormElement>) => {
232
+ event.preventDefault();
233
+ setRobinhoodLoading(true);
234
+ setRobinhoodMessage('');
235
+ try {
236
+ const response = await fetch(buildBackendUrl('/api/brokers/robinhood/crypto/credentials'), {
237
+ method: 'POST',
238
+ headers: { 'Content-Type': 'application/json' },
239
+ body: JSON.stringify({ apiKey: robinhoodApiKey, privateKey: robinhoodPrivateKey })
240
+ });
241
+ const payload = await response.json();
242
+ if (!response.ok) {
243
+ throw new Error(payload.message ?? 'Unable to save Robinhood credentials.');
244
+ }
245
+ setRobinhoodStatus(payload);
246
+ setRobinhoodPrivateKey('');
247
+ setRobinhoodMessage('Robinhood credentials saved.');
248
+ } catch (err) {
249
+ setRobinhoodMessage(err instanceof Error ? err.message : 'Unable to save Robinhood credentials.');
250
+ } finally {
251
+ setRobinhoodLoading(false);
252
+ }
253
+ };
254
+
255
+ const handleRobinhoodBuy = async (event: FormEvent<HTMLFormElement>) => {
256
+ event.preventDefault();
257
+ setRobinhoodLoading(true);
258
+ setRobinhoodMessage('');
259
+ setRobinhoodOrderId('');
260
+ try {
261
+ const response = await fetch(buildBackendUrl('/api/brokers/robinhood/crypto/orders'), {
262
+ method: 'POST',
263
+ headers: { 'Content-Type': 'application/json' },
264
+ body: JSON.stringify({ symbol: robinhoodSymbol, cashAmount: Number(robinhoodCashAmount) })
265
+ });
266
+ const payload = await response.json();
267
+ if (!response.ok) {
268
+ throw new Error(payload.message ?? 'Unable to place Robinhood order.');
269
+ }
270
+ setRobinhoodOrderId(typeof payload.id === 'string' ? payload.id : 'submitted');
271
+ setRobinhoodMessage('Robinhood order submitted.');
272
+ } catch (err) {
273
+ setRobinhoodMessage(err instanceof Error ? err.message : 'Unable to place Robinhood order.');
274
+ } finally {
275
+ setRobinhoodLoading(false);
276
+ }
277
+ };
278
+
279
+ const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
280
+ event.preventDefault();
281
+ setLoading(true);
282
+ setError('');
283
+ setSuccess('');
284
+
285
+ try {
286
+ const response = await fetch(buildBackendUrl('/api/license'), {
287
+ method: 'POST',
288
+ headers: { 'Content-Type': 'application/json' },
289
+ body: JSON.stringify({ token: licenseToken })
290
+ });
291
+
292
+ const payload = await response.json();
293
+ if (!response.ok) {
294
+ throw new Error(payload.message ?? 'License validation failed.');
295
+ }
296
+
297
+ setStatus(payload);
298
+ setSuccess('License token validated and stored securely.');
299
+ setLicenseToken('');
300
+ } catch (err) {
301
+ setError(err instanceof Error ? err.message : 'License validation failed.');
302
+ } finally {
303
+ setLoading(false);
304
+ }
305
+ };
306
+
307
+ const handleHoldSave = async (event: FormEvent<HTMLFormElement>) => {
308
+ event.preventDefault();
309
+ setHoldSaving(true);
310
+ setHoldMessage('');
311
+ try {
312
+ const response = await fetch(buildBackendUrl('/api/settings/transaction-hold'), {
313
+ method: 'POST',
314
+ headers: { 'Content-Type': 'application/json' },
315
+ body: JSON.stringify({ enabled: holdEnabled, holdHours })
316
+ });
317
+ if (!response.ok) {
318
+ throw new Error('Unable to update hold settings.');
319
+ }
320
+ setHoldMessage('Transaction hold configuration saved.');
321
+ } catch (err) {
322
+ setHoldMessage(err instanceof Error ? err.message : 'Unable to update hold settings.');
323
+ } finally {
324
+ setHoldSaving(false);
325
+ }
326
+ };
327
+
328
+ const handleVanitySubmit = async (event: FormEvent<HTMLFormElement>) => {
329
+ event.preventDefault();
330
+ setVanitySubmitting(true);
331
+ setVanityMessage('');
332
+ try {
333
+ const body = {
334
+ prefix: vanityForm.prefix || undefined,
335
+ suffix: vanityForm.suffix || undefined,
336
+ regex: vanityForm.regex || undefined,
337
+ derivationPath: vanityForm.derivationPath || undefined,
338
+ maxAttempts: vanityForm.maxAttempts > 0 ? vanityForm.maxAttempts : undefined,
339
+ label: vanityForm.label || undefined
340
+ };
341
+ const response = await fetch(buildBackendUrl('/api/wallets/vanity'), {
342
+ method: 'POST',
343
+ headers: { 'Content-Type': 'application/json' },
344
+ body: JSON.stringify(body)
345
+ });
346
+ if (!response.ok) {
347
+ const payload = await response.json().catch(() => ({}));
348
+ throw new Error(payload.message ?? 'Failed to start vanity job');
349
+ }
350
+ setVanityMessage('Vanity search started');
351
+ setVanityForm((prev) => ({ ...prev, label: '' }));
352
+ void refreshVanityJobs();
353
+ } catch (err) {
354
+ setVanityMessage(err instanceof Error ? err.message : 'Failed to start vanity job');
355
+ } finally {
356
+ setVanitySubmitting(false);
357
+ }
358
+ };
359
+
360
+ const cancelVanityJob = async (id: string) => {
361
+ try {
362
+ const response = await fetch(buildBackendUrl(`/api/wallets/vanity/${id}`), {
363
+ method: 'DELETE'
364
+ });
365
+ if (!response.ok) {
366
+ throw new Error('Unable to cancel job');
367
+ }
368
+ setVanityMessage('Cancellation requested');
369
+ void refreshVanityJobs();
370
+ } catch (err) {
371
+ setVanityMessage(err instanceof Error ? err.message : 'Unable to cancel job');
372
+ }
373
+ };
374
+
375
+ const vanityStats = useMemo(() => {
376
+ const active = vanityJobs.filter((job) => job.status === 'running');
377
+ const completed = vanityJobs.filter((job) => job.status === 'completed');
378
+ return {
379
+ active: active.length,
380
+ completed: completed.length
381
+ };
382
+ }, [vanityJobs]);
383
+
384
+ return (
385
+ <div className="space-y-6">
386
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
387
+ <h2 className="text-lg font-semibold">Backend Connection</h2>
388
+ <p className="mt-2 text-sm text-slate-400">
389
+ Point GNOMAN at the backend you want to use. Auto-detect will scan common local hosts for the health endpoint.
390
+ </p>
391
+ <form className="mt-4 space-y-3" onSubmit={handleBackendSave}>
392
+ <label className="text-sm font-medium text-slate-300" htmlFor="backend-url">
393
+ Backend base URL
394
+ </label>
395
+ <input
396
+ id="backend-url"
397
+ type="url"
398
+ className="w-full rounded-md border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
399
+ value={backendUrl}
400
+ onChange={(event) => setBackendUrl(event.target.value)}
401
+ placeholder="http://127.0.0.1:4399"
402
+ disabled={backendChecking}
403
+ />
404
+ <div className="flex flex-wrap gap-2">
405
+ <button
406
+ type="button"
407
+ className="rounded-md border border-slate-700 px-3 py-2 text-xs font-semibold text-slate-200 transition hover:border-slate-500 disabled:cursor-not-allowed disabled:text-slate-500"
408
+ onClick={() => void handleBackendDetect()}
409
+ disabled={backendChecking}
410
+ >
411
+ {backendChecking ? 'Scanning…' : 'Auto-detect backend'}
412
+ </button>
413
+ <button
414
+ type="submit"
415
+ className="rounded-md bg-blue-600 px-4 py-2 text-xs font-semibold text-white transition hover:bg-blue-500 disabled:cursor-not-allowed disabled:bg-blue-900"
416
+ disabled={backendChecking}
417
+ >
418
+ {backendChecking ? 'Checking…' : 'Save backend'}
419
+ </button>
420
+ </div>
421
+ {backendMessage && (
422
+ <p className={`text-sm ${backendMessage.includes('Connected') || backendMessage.includes('Auto-detected') ? 'text-emerald-400' : 'text-red-400'}`}>
423
+ {backendMessage}
424
+ </p>
425
+ )}
426
+ </form>
427
+ </section>
428
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
429
+ <h2 className="text-lg font-semibold">Integrations & Runtime Features</h2>
430
+ <p className="mt-2 text-sm text-slate-400">
431
+ This section centralizes feature state for SAFE mode, Etherscan ABI lookups, and Robinhood integration so operators can verify what is actually active at runtime.
432
+ </p>
433
+ <div className="mt-4 grid gap-3 text-xs md:grid-cols-3">
434
+ <div className="rounded-md border border-slate-800 bg-slate-950/70 p-3">
435
+ <p className="font-semibold text-white">Safe</p>
436
+ <p className="mt-1 text-slate-300">Enabled: {String(runtimeCapabilities?.safe.enabled ?? false)}</p>
437
+ <p className="text-slate-400">Reason: {runtimeCapabilities?.safe.reason ?? 'unknown'}</p>
438
+ </div>
439
+ <div className="rounded-md border border-slate-800 bg-slate-950/70 p-3">
440
+ <p className="font-semibold text-white">Etherscan</p>
441
+ <p className="mt-1 text-slate-300">Enabled: {String(runtimeCapabilities?.etherscan.enabled ?? false)}</p>
442
+ <p className="text-slate-400">Reason: {runtimeCapabilities?.etherscan.reason ?? 'unknown'}</p>
443
+ </div>
444
+ <div className="rounded-md border border-slate-800 bg-slate-950/70 p-3">
445
+ <p className="font-semibold text-white">Robinhood</p>
446
+ <p className="mt-1 text-slate-300">Enabled: {String(runtimeCapabilities?.robinhood.enabled ?? false)}</p>
447
+ <p className="text-slate-400">Reason: {runtimeCapabilities?.robinhood.reason ?? 'unknown'}</p>
448
+ </div>
449
+ </div>
450
+ </section>
451
+
452
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
453
+ <h2 className="text-lg font-semibold">Integration Configuration: Robinhood Crypto Trading API</h2>
454
+ <p className="mt-2 text-sm text-slate-400">
455
+ Configure official Robinhood Crypto Trading API credentials. Stocks/options are intentionally unsupported.
456
+ </p>
457
+ <p className="mt-2 text-xs text-slate-500">
458
+ Crypto credentials status: {robinhoodStatus.configured ? `Configured (${robinhoodStatus.apiKeyPreview ?? 'hidden'})` : 'Not configured'}
459
+ </p>
460
+ <p className="mt-2 text-xs text-slate-400">Auth: {robinhoodStatus.auth?.ok ? 'OK' : `FAIL (${robinhoodStatus.auth?.reason ?? 'not attempted'})`}</p>
461
+ <form className="mt-4 grid gap-3 md:grid-cols-2" onSubmit={handleRobinhoodCredentialsSave}>
462
+ <div>
463
+ <label className="text-sm font-medium text-slate-300" htmlFor="robinhood-api-key">API key</label>
464
+ <input
465
+ id="robinhood-api-key"
466
+ type="text"
467
+ className="mt-1 w-full rounded-md border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
468
+ value={robinhoodApiKey}
469
+ onChange={(event) => setRobinhoodApiKey(event.target.value)}
470
+ disabled={robinhoodLoading}
471
+ required
472
+ />
473
+ </div>
474
+ <div>
475
+ <label className="text-sm font-medium text-slate-300" htmlFor="robinhood-private-key">Private key (PEM)</label>
476
+ <input
477
+ id="robinhood-private-key"
478
+ type="password"
479
+ className="mt-1 w-full rounded-md border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
480
+ value={robinhoodPrivateKey}
481
+ onChange={(event) => setRobinhoodPrivateKey(event.target.value)}
482
+ disabled={robinhoodLoading}
483
+ required
484
+ />
485
+ </div>
486
+ <button
487
+ type="submit"
488
+ className="md:col-span-2 rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-blue-500 disabled:cursor-not-allowed disabled:bg-blue-900"
489
+ disabled={robinhoodLoading}
490
+ >
491
+ {robinhoodLoading ? 'Saving…' : 'Save Robinhood credentials'}
492
+ </button>
493
+ </form>
494
+
495
+ <form className="mt-4 grid gap-3 md:grid-cols-3" onSubmit={handleRobinhoodBuy}>
496
+ <div>
497
+ <label className="text-sm font-medium text-slate-300" htmlFor="robinhood-symbol">Symbol</label>
498
+ <input
499
+ id="robinhood-symbol"
500
+ type="text"
501
+ className="mt-1 w-full rounded-md border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
502
+ value={robinhoodSymbol}
503
+ onChange={(event) => setRobinhoodSymbol(event.target.value)}
504
+ disabled={robinhoodLoading}
505
+ required
506
+ />
507
+ </div>
508
+ <div>
509
+ <label className="text-sm font-medium text-slate-300" htmlFor="robinhood-cash-amount">Cash amount (USD)</label>
510
+ <input
511
+ id="robinhood-cash-amount"
512
+ type="number"
513
+ min="0.01"
514
+ step="0.01"
515
+ className="mt-1 w-full rounded-md border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
516
+ value={robinhoodCashAmount}
517
+ onChange={(event) => setRobinhoodCashAmount(event.target.value)}
518
+ disabled={robinhoodLoading || !robinhoodStatus.configured}
519
+ required
520
+ />
521
+ </div>
522
+ <button
523
+ type="submit"
524
+ className="self-end rounded-md bg-emerald-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-emerald-500 disabled:cursor-not-allowed disabled:bg-emerald-900"
525
+ disabled={robinhoodLoading || !robinhoodStatus.configured}
526
+ >
527
+ {robinhoodLoading ? 'Submitting…' : 'Buy crypto with cash'}
528
+ </button>
529
+ </form>
530
+ {robinhoodMessage && (
531
+ <p className={`mt-3 text-sm ${robinhoodMessage.includes('saved') || robinhoodMessage.includes('submitted') ? 'text-emerald-400' : 'text-red-400'}`}>
532
+ {robinhoodMessage}
533
+ {robinhoodOrderId ? ` (Order: ${robinhoodOrderId})` : ''}
534
+ </p>
535
+ )}
536
+ </section>
537
+
538
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
539
+ <h2 className="text-lg font-semibold">Runtime Diagnostics</h2>
540
+ <p className="mt-2 text-xs text-slate-400">Robinhood support is official crypto API only; stocks/options automation is not exposed via Robinhood public API.</p>
541
+ <div className="mt-3 grid gap-4 text-xs text-slate-300 md:grid-cols-2">
542
+ <div>
543
+ <p className="font-semibold text-white">ABI cache</p>
544
+ <p>Hits: {runtimeTelemetry?.abi.cacheHits ?? 0} · Misses: {runtimeTelemetry?.abi.cacheMisses ?? 0}</p>
545
+ <ul className="mt-2 space-y-1">
546
+ {(runtimeTelemetry?.abi.lastResolves ?? []).slice(0, 20).map((entry) => (
547
+ <li key={`${entry.chainId}-${entry.address}`}>{entry.address} · {entry.contractName} · {entry.source} · cached={String(entry.cached)}</li>
548
+ ))}
549
+ </ul>
550
+ </div>
551
+ <div>
552
+ <p className="font-semibold text-white">Secrets status</p>
553
+ <ul className="mt-2 space-y-1">
554
+ {(runtimeTelemetry?.secrets ?? []).map((entry) => (
555
+ <li key={entry.key}>{entry.key}: {entry.present ? 'present' : 'missing'} via {entry.source}</li>
556
+ ))}
557
+ </ul>
558
+ <p className="mt-3 font-semibold text-white">Safe runtime</p>
559
+ <p>Version: {runtimeTelemetry?.safe.version ?? 'unknown'}</p>
560
+ <p>Mastercopy: {runtimeTelemetry?.safe.mastercopyAddress ?? 'unknown'}</p>
561
+ <p>Module enabled: {String(runtimeTelemetry?.safe.moduleEnabled ?? false)}</p>
562
+ <p className="mt-3 font-semibold text-white">Robinhood Crypto</p>
563
+ <p>Enabled: {String(runtimeTelemetry?.robinhood.enabled ?? false)} · Auth: {runtimeTelemetry?.robinhood.auth.ok ? 'OK' : `FAIL (${runtimeTelemetry?.robinhood.auth.reason ?? 'unknown'})`}</p>
564
+ <ul className="mt-2 space-y-1">
565
+ {(runtimeTelemetry?.robinhood.requests ?? []).slice(0, 20).map((entry, idx) => (
566
+ <li key={`${entry.endpoint}-${idx}`}>{entry.endpoint} · {entry.statusCode} · {entry.latencyMs}ms</li>
567
+ ))}
568
+ </ul>
569
+ </div>
570
+ </div>
571
+ </section>
572
+
573
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
574
+ <h2 className="text-lg font-semibold">Offline License Activation</h2>
575
+ <p className="mt-2 text-sm text-slate-400">
576
+ Activate GNOMAN 2.0 by pasting an offline license token. Licenses are signed with Ed25519 and
577
+ verified locally before being stored in encrypted storage.
578
+ </p>
579
+ <div className="mt-4 rounded-lg border border-slate-800 bg-slate-950/60 p-4">
580
+ <p className="text-sm text-slate-400">
581
+ {status.active ? (
582
+ <span>
583
+ Licensed to{' '}
584
+ <span className="font-medium text-slate-200">{status.identifier ?? 'Unassigned'}</span>
585
+ {status.product && status.version && (
586
+ <>
587
+ {' '}for{' '}
588
+ <span className="font-medium text-slate-200">
589
+ {status.product} {status.version}
590
+ </span>
591
+ </>
592
+ )}{' '}
593
+ <span className="font-medium text-slate-200">
594
+ {status.expiry ? `Expires ${new Date(status.expiry).toLocaleString()}` : 'No expiry recorded'}
595
+ </span>
596
+ </span>
597
+ ) : (
598
+ 'No offline license detected yet.'
599
+ )}
600
+ </p>
601
+ </div>
602
+ <form className="mt-4 space-y-4" onSubmit={handleSubmit}>
603
+ <div>
604
+ <label className="text-sm font-medium text-slate-300" htmlFor="license-token">
605
+ License token
606
+ </label>
607
+ <input
608
+ id="license-token"
609
+ type="text"
610
+ className="mt-1 w-full rounded-md border border-slate-800 bg-slate-950 px-3 py-2 text-sm font-mono text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
611
+ value={licenseToken}
612
+ onChange={(event) => setLicenseToken(event.target.value)}
613
+ placeholder="Paste raw token or grouped Base32 string"
614
+ disabled={loading}
615
+ required
616
+ />
617
+ <p className="mt-1 text-xs text-slate-500">
618
+ Tokens are validated locally with the bundled Ed25519 public key; no network requests leave this device.
619
+ </p>
620
+ </div>
621
+ {error && <p className="text-sm text-red-400">{error}</p>}
622
+ {success && <p className="text-sm text-emerald-400">{success}</p>}
623
+ <button
624
+ type="submit"
625
+ className="w-full rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-blue-500 disabled:cursor-not-allowed disabled:bg-blue-900"
626
+ disabled={loading}
627
+ >
628
+ {loading ? 'Validating…' : status.active ? 'Replace license token' : 'Activate license'}
629
+ </button>
630
+ </form>
631
+ </section>
632
+
633
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
634
+ <h2 className="text-lg font-semibold">Transaction Hold Period</h2>
635
+ <p className="mt-2 text-sm text-slate-400">
636
+ Queue outgoing Safe transactions for a review window before execution. When enabled, transactions wait for the
637
+ configured number of hours and display a live countdown in the queue.
638
+ </p>
639
+ <form className="mt-4 space-y-4" onSubmit={handleHoldSave}>
640
+ <label className="flex items-center gap-3 text-sm font-medium text-slate-200">
641
+ <input
642
+ type="checkbox"
643
+ className="h-4 w-4 rounded border-slate-700 bg-slate-900 text-blue-500 focus:ring-blue-500"
644
+ checked={holdEnabled}
645
+ onChange={(event) => setHoldEnabled(event.target.checked)}
646
+ />
647
+ Enable 24-hour hold across all Safes
648
+ </label>
649
+ <div>
650
+ <label className="text-sm font-medium text-slate-300" htmlFor="hold-hours">
651
+ Hold duration (hours)
652
+ </label>
653
+ <input
654
+ id="hold-hours"
655
+ type="number"
656
+ min={1}
657
+ className="mt-1 w-full rounded-md border border-slate-800 bg-slate-950 px-3 py-2 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
658
+ value={holdHours}
659
+ onChange={(event) => setHoldHours(Number.parseInt(event.target.value, 10) || 24)}
660
+ disabled={!holdEnabled}
661
+ />
662
+ <p className="mt-1 text-xs text-slate-500">
663
+ Stored securely via the active keyring service (entry SAFE_TX_HOLD_ENABLED).
664
+ </p>
665
+ </div>
666
+ {holdMessage && (
667
+ <p className={`text-sm ${holdMessage.includes('saved') ? 'text-emerald-400' : 'text-red-400'}`}>{holdMessage}</p>
668
+ )}
669
+ <button
670
+ type="submit"
671
+ className="inline-flex items-center justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-blue-500 disabled:cursor-not-allowed disabled:bg-blue-900"
672
+ disabled={holdSaving}
673
+ >
674
+ {holdSaving ? 'Saving…' : 'Save hold policy'}
675
+ </button>
676
+ </form>
677
+ </section>
678
+
679
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
680
+ <div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
681
+ <div>
682
+ <h2 className="text-lg font-semibold">Vanity Wallet Generator</h2>
683
+ <p className="text-sm text-slate-400">
684
+ Launch GPU-friendly worker searches for desired address patterns. Secrets never leave the local secure
685
+ store; only aliases are persisted.
686
+ </p>
687
+ </div>
688
+ <div className="rounded border border-slate-800 bg-slate-950/60 px-3 py-2 text-xs text-slate-400">
689
+ Active jobs: <span className="font-semibold text-slate-200">{vanityStats.active}</span> · Completed:{' '}
690
+ <span className="font-semibold text-slate-200">{vanityStats.completed}</span>
691
+ </div>
692
+ </div>
693
+ <form className="mt-4 grid gap-4 md:grid-cols-2" onSubmit={handleVanitySubmit}>
694
+ <label className="text-sm text-slate-300">
695
+ Prefix
696
+ <input
697
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 font-mono text-xs"
698
+ placeholder="0xdead"
699
+ value={vanityForm.prefix}
700
+ onChange={(event) => setVanityForm((prev) => ({ ...prev, prefix: event.target.value }))}
701
+ />
702
+ </label>
703
+ <label className="text-sm text-slate-300">
704
+ Suffix
705
+ <input
706
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 font-mono text-xs"
707
+ placeholder="beef"
708
+ value={vanityForm.suffix}
709
+ onChange={(event) => setVanityForm((prev) => ({ ...prev, suffix: event.target.value }))}
710
+ />
711
+ </label>
712
+ <label className="text-sm text-slate-300">
713
+ Regex pattern
714
+ <input
715
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 font-mono text-xs"
716
+ placeholder="^0x[a-f]{6}"
717
+ value={vanityForm.regex}
718
+ onChange={(event) => setVanityForm((prev) => ({ ...prev, regex: event.target.value }))}
719
+ />
720
+ </label>
721
+ <label className="text-sm text-slate-300">
722
+ Derivation path
723
+ <input
724
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 font-mono text-xs"
725
+ value={vanityForm.derivationPath}
726
+ onChange={(event) => setVanityForm((prev) => ({ ...prev, derivationPath: event.target.value }))}
727
+ />
728
+ </label>
729
+ <label className="text-sm text-slate-300">
730
+ Label (alias)
731
+ <input
732
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
733
+ placeholder="Treasury Vanity"
734
+ value={vanityForm.label}
735
+ onChange={(event) => setVanityForm((prev) => ({ ...prev, label: event.target.value }))}
736
+ />
737
+ </label>
738
+ <label className="text-sm text-slate-300">
739
+ Max attempts (optional)
740
+ <input
741
+ type="number"
742
+ min={0}
743
+ className="mt-1 w-full rounded border border-slate-700 bg-slate-900 p-2 text-xs"
744
+ value={vanityForm.maxAttempts}
745
+ onChange={(event) =>
746
+ setVanityForm((prev) => ({ ...prev, maxAttempts: Number.parseInt(event.target.value, 10) || 0 }))
747
+ }
748
+ />
749
+ </label>
750
+ <button
751
+ type="submit"
752
+ className="col-span-full rounded bg-blue-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-blue-500 disabled:cursor-not-allowed disabled:bg-blue-900"
753
+ disabled={vanitySubmitting}
754
+ >
755
+ {vanitySubmitting ? 'Launching…' : 'Start vanity search'}
756
+ </button>
757
+ </form>
758
+ {vanityMessage && (
759
+ <p className={`mt-2 text-sm ${vanityMessage.includes('started') ? 'text-emerald-400' : 'text-amber-400'}`}>
760
+ {vanityMessage}
761
+ </p>
762
+ )}
763
+ <div className="mt-4 overflow-x-auto">
764
+ <table className="min-w-full divide-y divide-slate-800 text-left text-xs">
765
+ <thead>
766
+ <tr className="text-slate-400">
767
+ <th className="px-3 py-2 font-medium">Job</th>
768
+ <th className="px-3 py-2 font-medium">Pattern</th>
769
+ <th className="px-3 py-2 font-medium">Progress</th>
770
+ <th className="px-3 py-2 font-medium">Status</th>
771
+ <th className="px-3 py-2 font-medium">Actions</th>
772
+ </tr>
773
+ </thead>
774
+ <tbody className="divide-y divide-slate-800">
775
+ {vanityJobs.map((job) => {
776
+ const eta = job.etaMs && job.etaMs > 0 ? Math.ceil(job.etaMs / 1000) : 0;
777
+ return (
778
+ <tr key={job.id} className="text-slate-300">
779
+ <td className="px-3 py-2 align-top">
780
+ <div className="font-semibold text-slate-100">{job.label ?? job.id.slice(0, 8)}</div>
781
+ <div className="font-mono text-[10px] text-slate-500">{job.id}</div>
782
+ <div className="text-[10px] text-slate-500">
783
+ Started {new Date(job.startedAt).toLocaleString()}
784
+ </div>
785
+ {job.completedAt && (
786
+ <div className="text-[10px] text-slate-500">
787
+ Finished {new Date(job.completedAt).toLocaleString()}
788
+ </div>
789
+ )}
790
+ </td>
791
+ <td className="px-3 py-2 align-top text-[11px] text-slate-400">
792
+ <div>Prefix: {job.pattern.prefix || '—'}</div>
793
+ <div>Suffix: {job.pattern.suffix || '—'}</div>
794
+ <div>Regex: {job.pattern.regex || '—'}</div>
795
+ <div>Path: {job.pattern.derivationPath}</div>
796
+ </td>
797
+ <td className="px-3 py-2 align-top text-[11px]">
798
+ <div>Attempts: {job.attempts.toLocaleString()}</div>
799
+ <div>
800
+ Rate: {job.attemptRate ? `${job.attemptRate.toFixed(0)} /s` : '—'}
801
+ </div>
802
+ <div>
803
+ ETA: {eta > 0 ? `${eta}s` : job.status === 'running' ? '…' : '0s'}
804
+ </div>
805
+ {job.targetAttempts && (
806
+ <div>Expected: {Math.round(job.targetAttempts).toLocaleString()}</div>
807
+ )}
808
+ </td>
809
+ <td className="px-3 py-2 align-top text-[11px] text-slate-300">
810
+ <div className="font-medium capitalize">{job.status}</div>
811
+ {job.address && (
812
+ <div className="font-mono text-[10px] text-emerald-400">{job.address}</div>
813
+ )}
814
+ {job.mnemonicAlias && (
815
+ <div className="text-[10px] text-slate-400">Secret alias: {job.mnemonicAlias}</div>
816
+ )}
817
+ {job.message && <div className="text-[10px] text-amber-400">{job.message}</div>}
818
+ </td>
819
+ <td className="px-3 py-2 align-top text-[11px]">
820
+ {job.status === 'running' ? (
821
+ <button
822
+ className="rounded border border-amber-500 px-2 py-1 text-amber-300 transition hover:bg-amber-500/20"
823
+ onClick={() => cancelVanityJob(job.id)}
824
+ disabled={vanityLoading}
825
+ >
826
+ Cancel
827
+ </button>
828
+ ) : job.address ? (
829
+ <span className="text-emerald-400">Ready</span>
830
+ ) : (
831
+ <span className="text-slate-500">—</span>
832
+ )}
833
+ </td>
834
+ </tr>
835
+ );
836
+ })}
837
+ {vanityJobs.length === 0 && (
838
+ <tr>
839
+ <td className="px-3 py-4 text-sm text-slate-500" colSpan={5}>
840
+ {vanityLoading ? 'Loading vanity jobs…' : 'No vanity jobs yet. Start one above.'}
841
+ </td>
842
+ </tr>
843
+ )}
844
+ </tbody>
845
+ </table>
846
+ </div>
847
+ </section>
848
+
849
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
850
+ <h2 className="text-lg font-semibold">Developer Sandbox</h2>
851
+ <p className="mt-2 text-sm text-slate-500">
852
+ Use Hardhat or anvil to fork a live network for Safe testing. Configure RPC credentials in an upcoming release.
853
+ </p>
854
+ </section>
855
+ <section className="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
856
+ <h2 className="text-lg font-semibold">Knowledge Base</h2>
857
+ <p className="mt-1 text-sm text-slate-500">
858
+ Visit the in-app wiki for onboarding tips, security practices, and GNOMAN 2.0 walkthroughs.
859
+ </p>
860
+ <Link
861
+ to="/guide"
862
+ className="mt-3 inline-flex items-center gap-2 rounded-md border border-blue-500 px-3 py-2 text-sm font-medium text-blue-300 transition hover:border-blue-400 hover:text-blue-200"
863
+ >
864
+ Open wiki user guide
865
+ </Link>
866
+ </section>
867
+ </div>
868
+ );
869
+ };
870
+
871
+ export default Settings;