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,57 @@
1
+ import type { Request, Response } from 'express';
2
+
3
+ jest.mock('../backend/services/walletService', () => ({
4
+ importWalletFromMnemonic: jest.fn(async (payload: unknown) => payload)
5
+ }));
6
+
7
+ import { importWalletFromMnemonic } from '../backend/services/walletService';
8
+ import { importMnemonic } from '../backend/controllers/walletController';
9
+
10
+ const mockedImportWalletFromMnemonic = importWalletFromMnemonic as jest.MockedFunction<
11
+ typeof importWalletFromMnemonic
12
+ >;
13
+
14
+ describe('walletController importMnemonic', () => {
15
+ afterEach(() => {
16
+ mockedImportWalletFromMnemonic.mockClear();
17
+ });
18
+
19
+ it('accepts derivationPath field from request body', async () => {
20
+ const req = {
21
+ body: {
22
+ mnemonic: 'test test test test test test test test test test test junk',
23
+ alias: 'alpha',
24
+ password: 'pw',
25
+ derivationPath: "m/44'/60'/0'/0/7",
26
+ hidden: true
27
+ }
28
+ } as Request;
29
+ const json = jest.fn();
30
+ const res = { json } as unknown as Response;
31
+
32
+ await importMnemonic(req, res, jest.fn());
33
+
34
+ expect(mockedImportWalletFromMnemonic).toHaveBeenCalledWith(
35
+ expect.objectContaining({ derivationPath: "m/44'/60'/0'/0/7" })
36
+ );
37
+ expect(json).toHaveBeenCalled();
38
+ });
39
+
40
+ it('falls back to legacy path field when derivationPath is not provided', async () => {
41
+ const req = {
42
+ body: {
43
+ mnemonic: 'test test test test test test test test test test test junk',
44
+ path: "m/44'/60'/0'/0/3"
45
+ }
46
+ } as Request;
47
+ const json = jest.fn();
48
+ const res = { json } as unknown as Response;
49
+
50
+ await importMnemonic(req, res, jest.fn());
51
+
52
+ expect(mockedImportWalletFromMnemonic).toHaveBeenCalledWith(
53
+ expect.objectContaining({ derivationPath: "m/44'/60'/0'/0/3" })
54
+ );
55
+ expect(json).toHaveBeenCalled();
56
+ });
57
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist/backend"
5
+ },
6
+ "include": ["backend/**/*.ts", "backend/types/**/*.d.ts", "modules/**/*.ts", "src/**/*.ts", "cli/**/*.ts"]
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist/cli"
5
+ },
6
+ "include": ["cli/**/*.ts", "src/**/*.ts"]
7
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "lib": ["ES2020", "DOM"],
6
+ "strict": true,
7
+ "moduleResolution": "Node",
8
+ "esModuleInterop": true,
9
+ "types": ["node", "cors", "better-sqlite3", "jest"],
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "allowSyntheticDefaultImports": true,
14
+ "sourceMap": true,
15
+ "outDir": "dist"
16
+ },
17
+ "exclude": ["renderer", "node_modules", "dist"]
18
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist/main"
5
+ },
6
+ "include": ["main/**/*.ts", "src/**/*.ts"]
7
+ }
File without changes
File without changes
@@ -0,0 +1,72 @@
1
+ """
2
+ Etherscan Tracker Integration for GNOMAN
3
+ Tracks Safe transactions and performs lookup queries.
4
+ """
5
+
6
+ import json
7
+ import os
8
+ import time
9
+ from pathlib import Path
10
+
11
+ import requests
12
+ from keyring import get_password
13
+
14
+ SAFE_STATE_PATH = Path("state/gnosis_safe_state.json")
15
+ LOG_PATH = Path("logs/safe_tx_log.json")
16
+ ETHERSCAN_BASE_URL = "https://api.etherscan.io/api"
17
+ POLL_INTERVAL = 30 # seconds
18
+
19
+
20
+ def get_etherscan_api_key() -> str:
21
+ service = os.getenv("GNOMAN_KEYRING_SERVICE", "gnoman")
22
+ api_key = get_password(service, "ETHERSCAN_API_KEY")
23
+ if not api_key:
24
+ raise RuntimeError("ETHERSCAN_API_KEY is not configured in the keyring.")
25
+ return api_key
26
+
27
+
28
+ def get_safe_address() -> str:
29
+ with open(SAFE_STATE_PATH, encoding="utf-8") as handle:
30
+ data = json.load(handle)
31
+ return data["address"]
32
+
33
+
34
+ def load_safe_state() -> dict:
35
+ if not SAFE_STATE_PATH.exists():
36
+ raise RuntimeError("Safe state missing — persistence failure detected.")
37
+ with open(SAFE_STATE_PATH, encoding="utf-8") as handle:
38
+ state = json.load(handle)
39
+ if not state.get("owners") or len(state["owners"]) < 3:
40
+ raise ValueError("Safe loaded without correct owner list (3 required).")
41
+ return state
42
+
43
+
44
+ def fetch_transactions(address: str, api_key: str | None = None):
45
+ api_key = api_key or get_etherscan_api_key()
46
+ url = (
47
+ f"{ETHERSCAN_BASE_URL}?module=account&action=txlist&address={address}"
48
+ f"&apikey={api_key}"
49
+ )
50
+ response = requests.get(url, timeout=20)
51
+ response.raise_for_status()
52
+ data = response.json()
53
+ if data["status"] != "1":
54
+ raise ValueError(f"Etherscan error: {data['message']}")
55
+ return data["result"]
56
+
57
+
58
+ def track_safe_transactions():
59
+ safe = load_safe_state()
60
+ api_key = get_etherscan_api_key()
61
+ LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
62
+ print(f"[EtherscanTracker] Tracking transactions for Safe: {safe['address']}")
63
+ while True:
64
+ txs = fetch_transactions(safe["address"], api_key)
65
+ with open(LOG_PATH, "w", encoding="utf-8") as handle:
66
+ json.dump(txs, handle, indent=2)
67
+ print(f"[EtherscanTracker] {len(txs)} transactions logged.")
68
+ time.sleep(POLL_INTERVAL)
69
+
70
+
71
+ if __name__ == "__main__":
72
+ track_safe_transactions()
File without changes
@@ -0,0 +1,111 @@
1
+ """Safe manager utilities for persisting and reloading Safe state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any, Callable, Dict, Iterable, Optional
8
+
9
+ SAFE_STATE_PATH = Path("state/gnosis_safe_state.json")
10
+
11
+
12
+ def _normalize_owners(raw_owners: Any) -> list[str]:
13
+ if raw_owners is None:
14
+ return []
15
+ if callable(raw_owners):
16
+ raw_owners = raw_owners()
17
+ if isinstance(raw_owners, dict):
18
+ raw_owners = list(raw_owners.values())
19
+ if isinstance(raw_owners, str):
20
+ return [raw_owners]
21
+ if isinstance(raw_owners, Iterable):
22
+ return [str(owner) for owner in raw_owners]
23
+ return []
24
+
25
+
26
+ def _extract_threshold(safe_instance: Any) -> int:
27
+ threshold = None
28
+ if hasattr(safe_instance, "getThreshold"):
29
+ threshold = safe_instance.getThreshold()
30
+ elif hasattr(safe_instance, "threshold"):
31
+ attr = safe_instance.threshold
32
+ threshold = attr() if callable(attr) else attr
33
+ if threshold is None:
34
+ raise ValueError("Safe instance does not expose a threshold value")
35
+ return int(threshold)
36
+
37
+
38
+ def persist_safe_state(safe_instance: Any) -> Dict[str, Any]:
39
+ """Persist the current Safe metadata to disk.
40
+
41
+ Parameters
42
+ ----------
43
+ safe_instance: Any
44
+ Object exposing ``address``, ``owners`` (method or iterable) and
45
+ ``getThreshold`` or ``threshold``.
46
+ """
47
+
48
+ if safe_instance is None:
49
+ raise ValueError("Safe instance is required to persist state")
50
+
51
+ address = getattr(safe_instance, "address", None)
52
+ if callable(address): # defensive: handle SDKs exposing callables
53
+ address = address()
54
+ if not address:
55
+ raise ValueError("Safe instance is missing an address")
56
+
57
+ owners = _normalize_owners(getattr(safe_instance, "owners", None))
58
+ if len(owners) < 3:
59
+ raise ValueError("Safe must have at least three owners before persisting")
60
+
61
+ threshold = _extract_threshold(safe_instance)
62
+
63
+ SAFE_STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
64
+ data = {"address": str(address), "owners": owners, "threshold": threshold}
65
+ with open(SAFE_STATE_PATH, "w", encoding="utf-8") as handle:
66
+ json.dump(data, handle, indent=2)
67
+ print("[SafeManager] Safe state persisted successfully.")
68
+ return data
69
+
70
+
71
+ def load_persisted_safe() -> Dict[str, Any]:
72
+ if not SAFE_STATE_PATH.exists():
73
+ raise RuntimeError("Safe state file missing.")
74
+ with open(SAFE_STATE_PATH, "r", encoding="utf-8") as handle:
75
+ data = json.load(handle)
76
+ owners = data.get("owners") or []
77
+ if len(owners) < 3:
78
+ raise ValueError("Invalid Safe state — missing or incomplete owners.")
79
+ print(f"[SafeManager] Loaded Safe with owners: {owners}")
80
+ return data
81
+
82
+
83
+ class SafeManager:
84
+ """Utility orchestrating Safe persistence and retrieval."""
85
+
86
+ def __init__(self, safe_factory: Callable[..., Any]):
87
+ self._safe_factory = safe_factory
88
+ self._safe_instance: Optional[Any] = None
89
+
90
+ @property
91
+ def safe(self) -> Any:
92
+ if self._safe_instance is None:
93
+ raise RuntimeError("Safe has not been initialised. Call `load_safe`." )
94
+ return self._safe_instance
95
+
96
+ def load_safe(self, *factory_args: Any, **factory_kwargs: Any) -> Any:
97
+ """Instantiate a Safe instance and persist its state immediately."""
98
+ safe_instance = self._safe_factory(*factory_args, **factory_kwargs)
99
+ self._safe_instance = safe_instance
100
+ persist_safe_state(safe_instance)
101
+ return safe_instance
102
+
103
+ def refresh_state(self) -> Dict[str, Any]:
104
+ """Persist the currently tracked Safe instance again and return the cache."""
105
+ if self._safe_instance is None:
106
+ raise RuntimeError("Safe has not been initialised.")
107
+ return persist_safe_state(self._safe_instance)
108
+
109
+ def get_cached_state(self) -> Dict[str, Any]:
110
+ """Load Safe metadata from the persisted cache on disk."""
111
+ return load_persisted_safe()
@@ -0,0 +1,181 @@
1
+ import json
2
+ import os
3
+ import sys
4
+ import time
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ PROJECT_ROOT = Path(__file__).resolve().parents[2]
10
+ if str(PROJECT_ROOT) not in sys.path:
11
+ sys.path.insert(0, str(PROJECT_ROOT))
12
+
13
+ from gnomon.utils import abi_resolver
14
+
15
+
16
+ @pytest.fixture(autouse=True)
17
+ def _isolated_abi_root(tmp_path, monkeypatch):
18
+ monkeypatch.chdir(tmp_path)
19
+ monkeypatch.delenv("ETHERSCAN_API_KEY", raising=False)
20
+ monkeypatch.delenv("ETHERSCAN_BASE_URL", raising=False)
21
+ monkeypatch.delenv("ETHERSCAN_CHAIN_ID", raising=False)
22
+ abi_resolver._ABI_CACHE.clear()
23
+ abi_resolver._FILE_CACHE.clear()
24
+ abi_resolver._FETCH_ONCE_CACHE.clear()
25
+ yield
26
+
27
+
28
+ def test_resolver_returns_cached_file_without_network_call(monkeypatch):
29
+ chain_id = 1
30
+ address = "0x1111111111111111111111111111111111111111"
31
+ cache_file = Path("abi/address/1") / f"{address}.json"
32
+ cache_file.parent.mkdir(parents=True, exist_ok=True)
33
+ cache_file.write_text(json.dumps({"abi": [{"name": "transfer", "type": "function"}]}), encoding="utf-8")
34
+
35
+ called = False
36
+
37
+ def _fail_get(*args, **kwargs):
38
+ nonlocal called
39
+ called = True
40
+ raise AssertionError("network should not be called")
41
+
42
+ monkeypatch.setattr(abi_resolver.requests, "get", _fail_get)
43
+
44
+ payload = abi_resolver.resolveAbiByAddress(chain_id, address, None)
45
+
46
+ assert payload["abi"][0]["name"] == "transfer"
47
+ assert called is False
48
+
49
+
50
+ def test_proxy_resolves_implementation_and_caches_under_original(monkeypatch):
51
+ os.environ["ETHERSCAN_API_KEY"] = "test-key"
52
+ chain_id = 1
53
+ original = "0x2222222222222222222222222222222222222222"
54
+ implementation = "0x3333333333333333333333333333333333333333"
55
+
56
+ class _Response:
57
+ def __init__(self, payload):
58
+ self._payload = payload
59
+
60
+ def raise_for_status(self):
61
+ return None
62
+
63
+ def json(self):
64
+ return self._payload
65
+
66
+ called_actions = []
67
+
68
+ def _mock_get(url, params, timeout):
69
+ called_actions.append((params["action"], params["address"]))
70
+ if params["action"] == "getsourcecode":
71
+ return _Response(
72
+ {
73
+ "status": "1",
74
+ "message": "OK",
75
+ "result": [{"Implementation": implementation}],
76
+ }
77
+ )
78
+ if params["action"] == "getabi":
79
+ assert params["address"] == implementation
80
+ return _Response(
81
+ {
82
+ "status": "1",
83
+ "message": "OK",
84
+ "result": json.dumps([{"name": "balanceOf", "type": "function"}]),
85
+ }
86
+ )
87
+ raise AssertionError("unexpected action")
88
+
89
+ monkeypatch.setattr(abi_resolver.requests, "get", _mock_get)
90
+
91
+ payload = abi_resolver.resolveAbiByAddress(chain_id, original, "ERC20")
92
+
93
+ assert payload["abi"][0]["name"] == "balanceOf"
94
+ cache_path = Path("abi/address/1") / f"{original}.json"
95
+ assert cache_path.exists()
96
+ assert ("getsourcecode", original) in called_actions
97
+ assert ("getabi", implementation) in called_actions
98
+
99
+
100
+ def test_rate_limiter_prevents_exceeding_limit_under_loop(monkeypatch):
101
+ os.environ["ETHERSCAN_API_KEY"] = "test-key"
102
+
103
+ class _Response:
104
+ def __init__(self, payload):
105
+ self._payload = payload
106
+
107
+ def raise_for_status(self):
108
+ return None
109
+
110
+ def json(self):
111
+ return self._payload
112
+
113
+ def _mock_get(url, params, timeout):
114
+ if params["action"] == "getsourcecode":
115
+ return _Response({"status": "1", "message": "OK", "result": [{"Implementation": ""}]})
116
+ return _Response({"status": "1", "message": "OK", "result": json.dumps([{"type": "function"}])})
117
+
118
+ monkeypatch.setattr(abi_resolver.requests, "get", _mock_get)
119
+
120
+ addresses = [f"0x{i:040x}" for i in range(10, 12)] # 2 resolves => 4 HTTP calls
121
+ start = time.perf_counter()
122
+ for address in addresses:
123
+ abi_resolver.resolveAbiByAddress(1, address, None)
124
+ elapsed = time.perf_counter() - start
125
+
126
+ assert elapsed >= 0.9
127
+
128
+
129
+ def test_meta_file_written_with_required_fields(monkeypatch):
130
+ os.environ["ETHERSCAN_API_KEY"] = "test-key"
131
+ address = "0x4444444444444444444444444444444444444444"
132
+
133
+ class _Response:
134
+ def __init__(self, payload):
135
+ self._payload = payload
136
+
137
+ def raise_for_status(self):
138
+ return None
139
+
140
+ def json(self):
141
+ return self._payload
142
+
143
+ def _mock_get(url, params, timeout):
144
+ if params["action"] == "getsourcecode":
145
+ return _Response({"status": "1", "message": "OK", "result": [{"Implementation": ""}]})
146
+ return _Response(
147
+ {
148
+ "status": "1",
149
+ "message": "OK",
150
+ "result": json.dumps([{"name": "approve", "type": "function"}]),
151
+ }
152
+ )
153
+
154
+ monkeypatch.setattr(abi_resolver.requests, "get", _mock_get)
155
+
156
+ abi_resolver.resolveAbiFileForAddress(1, address, "Token")
157
+
158
+ meta_file = Path("abi/address/1") / f"{address}.meta.json"
159
+ assert meta_file.exists()
160
+ meta = json.loads(meta_file.read_text(encoding="utf-8"))
161
+
162
+ for field in (
163
+ "chainId",
164
+ "address",
165
+ "abiTargetAddress",
166
+ "isProxy",
167
+ "implementation",
168
+ "abiNameHint",
169
+ "source",
170
+ "fetchedAt",
171
+ "abiSha256",
172
+ ):
173
+ assert field in meta
174
+ assert meta["address"] == address
175
+ assert meta["source"] == "etherscan"
176
+
177
+
178
+ def test_missing_api_key_raises_clear_error():
179
+ address = "0x5555555555555555555555555555555555555555"
180
+ with pytest.raises(RuntimeError, match="Missing ABI and no ETHERSCAN_API_KEY configured"):
181
+ abi_resolver.resolveAbiByAddress(1, address, None)
@@ -0,0 +1,97 @@
1
+ import json
2
+ import sys
3
+ import types
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ PROJECT_ROOT = Path(__file__).resolve().parents[2]
9
+ if str(PROJECT_ROOT) not in sys.path:
10
+ sys.path.insert(0, str(PROJECT_ROOT))
11
+
12
+ if "keyring" not in sys.modules:
13
+ keyring_stub = types.ModuleType("keyring")
14
+ _store: dict[tuple[str, str], str] = {}
15
+
16
+ def get_password(service_name: str, username: str):
17
+ return _store.get((service_name, username))
18
+
19
+ def set_password(service_name: str, username: str, password: str):
20
+ _store[(service_name, username)] = password
21
+
22
+ def delete_password(service_name: str, username: str):
23
+ _store.pop((service_name, username), None)
24
+
25
+ keyring_stub.get_password = get_password
26
+ keyring_stub.set_password = set_password
27
+ keyring_stub.delete_password = delete_password
28
+ sys.modules["keyring"] = keyring_stub
29
+
30
+ from keyring import delete_password, set_password
31
+
32
+ # Configure the keyring stub before importing the tracker module.
33
+ set_password("gnoman", "ETHERSCAN_API_KEY", "dummy-test-key")
34
+
35
+ from gnomon.api import etherscan_tracker
36
+ from gnomon.api.etherscan_tracker import (
37
+ fetch_transactions,
38
+ get_etherscan_api_key,
39
+ load_safe_state,
40
+ SAFE_STATE_PATH,
41
+ )
42
+
43
+
44
+ @pytest.fixture(autouse=True)
45
+ def cleanup_state_file():
46
+ SAFE_STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
47
+ yield
48
+ if SAFE_STATE_PATH.exists():
49
+ SAFE_STATE_PATH.unlink()
50
+ delete_password("gnoman", "ETHERSCAN_API_KEY")
51
+
52
+
53
+ def _write_state_file(address: str = "0xSAFE", owners=None, threshold: int = 2):
54
+ owners = owners or ["0x1", "0x2", "0x3"]
55
+ SAFE_STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
56
+ with open(SAFE_STATE_PATH, "w", encoding="utf-8") as handle:
57
+ json.dump({"address": address, "owners": owners, "threshold": threshold}, handle, indent=2)
58
+
59
+
60
+ def test_safe_persistence_and_tx_lookup(monkeypatch):
61
+ assert get_etherscan_api_key() == "dummy-test-key"
62
+
63
+ _write_state_file()
64
+
65
+ state = load_safe_state()
66
+ assert "owners" in state and len(state["owners"]) == 3
67
+
68
+ dummy_response = {
69
+ "status": "1",
70
+ "message": "OK",
71
+ "result": [
72
+ {"hash": "0xabc", "value": "0x0"},
73
+ {"hash": "0xdef", "value": "0x0"},
74
+ ],
75
+ }
76
+
77
+ class _StubResponse:
78
+ def __init__(self, payload):
79
+ self._payload = payload
80
+
81
+ def raise_for_status(self):
82
+ return None
83
+
84
+ def json(self):
85
+ return self._payload
86
+
87
+ def _mock_get(url, timeout):
88
+ assert state["address"] in url
89
+ assert get_etherscan_api_key() in url
90
+ return _StubResponse(dummy_response)
91
+
92
+ monkeypatch.setattr(etherscan_tracker.requests, "get", _mock_get)
93
+
94
+ txs = fetch_transactions(state["address"])
95
+ assert isinstance(txs, list)
96
+ assert all("hash" in tx for tx in txs)
97
+ print("[TEST] ✅ Safe persistence and Etherscan lookup verified.")
@@ -0,0 +1,5 @@
1
+ """Utility helpers for GNOMAN."""
2
+
3
+ from .abi_resolver import resolveAbiByAddress, resolveAbiFileForAddress
4
+
5
+ __all__ = ["resolveAbiByAddress", "resolveAbiFileForAddress"]