iwa 0.0.20__tar.gz → 0.0.23__tar.gz

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 (222) hide show
  1. {iwa-0.0.20/src/iwa.egg-info → iwa-0.0.23}/PKG-INFO +1 -1
  2. {iwa-0.0.20 → iwa-0.0.23}/pyproject.toml +2 -2
  3. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/chain/interface.py +8 -1
  4. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/monitor.py +2 -2
  5. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/safe.py +2 -2
  6. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/gnosis/safe.py +1 -1
  7. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/gnosis/tests/test_safe.py +1 -1
  8. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/importer.py +261 -33
  9. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/plugin.py +129 -43
  10. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_importer_error_handling.py +1 -1
  11. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_plugin_full.py +7 -9
  12. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/rpc.py +1 -1
  13. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/screens/wallets.py +2 -2
  14. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/tests/test_rpc.py +2 -2
  15. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/widgets/base.py +1 -1
  16. {iwa-0.0.20 → iwa-0.0.23/src/iwa.egg-info}/PKG-INFO +1 -1
  17. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_monitor.py +3 -3
  18. {iwa-0.0.20 → iwa-0.0.23}/LICENSE +0 -0
  19. {iwa-0.0.20 → iwa-0.0.23}/README.md +0 -0
  20. {iwa-0.0.20 → iwa-0.0.23}/setup.cfg +0 -0
  21. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/__init__.py +0 -0
  22. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/__main__.py +0 -0
  23. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/__init__.py +0 -0
  24. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/chain/__init__.py +0 -0
  25. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/chain/errors.py +0 -0
  26. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/chain/manager.py +0 -0
  27. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/chain/models.py +0 -0
  28. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/chain/rate_limiter.py +0 -0
  29. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/chainlist.py +0 -0
  30. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/cli.py +0 -0
  31. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/constants.py +0 -0
  32. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/contracts/__init__.py +0 -0
  33. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/contracts/abis/erc20.json +0 -0
  34. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/contracts/abis/multisend.json +0 -0
  35. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/contracts/abis/multisend_call_only.json +0 -0
  36. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/contracts/cache.py +0 -0
  37. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/contracts/contract.py +0 -0
  38. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/contracts/erc20.py +0 -0
  39. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/contracts/multisend.py +0 -0
  40. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/db.py +0 -0
  41. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/ipfs.py +0 -0
  42. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/keys.py +0 -0
  43. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/mnemonic.py +0 -0
  44. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/models.py +0 -0
  45. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/plugins.py +0 -0
  46. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/pricing.py +0 -0
  47. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/rpc_monitor.py +0 -0
  48. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/secrets.py +0 -0
  49. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/__init__.py +0 -0
  50. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/account.py +0 -0
  51. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/balance.py +0 -0
  52. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/plugin.py +0 -0
  53. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/transaction.py +0 -0
  54. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/transfer/__init__.py +0 -0
  55. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/transfer/base.py +0 -0
  56. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/transfer/erc20.py +0 -0
  57. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/transfer/multisend.py +0 -0
  58. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/transfer/native.py +0 -0
  59. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/services/transfer/swap.py +0 -0
  60. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/tables.py +0 -0
  61. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/test.py +0 -0
  62. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/tests/test_wallet.py +0 -0
  63. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/types.py +0 -0
  64. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/ui.py +0 -0
  65. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/utils.py +0 -0
  66. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/core/wallet.py +0 -0
  67. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/__init__.py +0 -0
  68. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/gnosis/__init__.py +0 -0
  69. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/gnosis/cow/__init__.py +0 -0
  70. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/gnosis/cow/quotes.py +0 -0
  71. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/gnosis/cow/swap.py +0 -0
  72. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/gnosis/cow/types.py +0 -0
  73. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/gnosis/cow_utils.py +0 -0
  74. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/gnosis/plugin.py +0 -0
  75. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/gnosis/tests/test_cow.py +0 -0
  76. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/__init__.py +0 -0
  77. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/constants.py +0 -0
  78. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/abis/activity_checker.json +0 -0
  79. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/abis/mech.json +0 -0
  80. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/abis/mech_marketplace.json +0 -0
  81. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/abis/mech_marketplace_v1.json +0 -0
  82. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/abis/mech_new.json +0 -0
  83. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/abis/service_manager.json +0 -0
  84. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/abis/service_registry.json +0 -0
  85. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/abis/service_registry_token_utility.json +0 -0
  86. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/abis/staking.json +0 -0
  87. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/abis/staking_token.json +0 -0
  88. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/activity_checker.py +0 -0
  89. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/base.py +0 -0
  90. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/mech.py +0 -0
  91. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/mech_marketplace.py +0 -0
  92. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/mech_marketplace_v1.py +0 -0
  93. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/service.py +0 -0
  94. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/contracts/staking.py +0 -0
  95. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/events.py +0 -0
  96. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/mech_reference.py +0 -0
  97. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/models.py +0 -0
  98. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/scripts/test_full_mech_flow.py +0 -0
  99. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/scripts/test_simple_lifecycle.py +0 -0
  100. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/service_manager/__init__.py +0 -0
  101. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/service_manager/base.py +0 -0
  102. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/service_manager/drain.py +0 -0
  103. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/service_manager/lifecycle.py +0 -0
  104. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/service_manager/mech.py +0 -0
  105. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/service_manager/staking.py +0 -0
  106. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/conftest.py +0 -0
  107. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_importer.py +0 -0
  108. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_mech_contracts.py +0 -0
  109. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_olas_contracts.py +0 -0
  110. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_olas_integration.py +0 -0
  111. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_olas_models.py +0 -0
  112. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_olas_view.py +0 -0
  113. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_olas_view_actions.py +0 -0
  114. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_olas_view_modals.py +0 -0
  115. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_plugin.py +0 -0
  116. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_service_lifecycle.py +0 -0
  117. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_service_manager.py +0 -0
  118. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_service_manager_errors.py +0 -0
  119. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_service_manager_flows.py +0 -0
  120. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_service_manager_mech.py +0 -0
  121. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_service_manager_rewards.py +0 -0
  122. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_service_manager_validation.py +0 -0
  123. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_service_staking.py +0 -0
  124. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_staking_integration.py +0 -0
  125. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tests/test_staking_validation.py +0 -0
  126. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tui/__init__.py +0 -0
  127. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/plugins/olas/tui/olas_view.py +0 -0
  128. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tools/__init__.py +0 -0
  129. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tools/check_profile.py +0 -0
  130. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tools/list_contracts.py +0 -0
  131. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tools/release.py +0 -0
  132. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tools/reset_env.py +0 -0
  133. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tools/reset_tenderly.py +0 -0
  134. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tools/restore_backup.py +0 -0
  135. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tools/test_chainlist.py +0 -0
  136. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tools/wallet_check.py +0 -0
  137. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/__init__.py +0 -0
  138. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/app.py +0 -0
  139. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/modals/__init__.py +0 -0
  140. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/modals/base.py +0 -0
  141. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/screens/__init__.py +0 -0
  142. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/tests/test_app.py +0 -0
  143. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/tests/test_wallets_refactor.py +0 -0
  144. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/tests/test_widgets.py +0 -0
  145. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/widgets/__init__.py +0 -0
  146. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/tui/workers.py +0 -0
  147. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/dependencies.py +0 -0
  148. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/models.py +0 -0
  149. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/routers/accounts.py +0 -0
  150. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/routers/olas/__init__.py +0 -0
  151. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/routers/olas/admin.py +0 -0
  152. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/routers/olas/funding.py +0 -0
  153. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/routers/olas/general.py +0 -0
  154. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/routers/olas/services.py +0 -0
  155. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/routers/olas/staking.py +0 -0
  156. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/routers/state.py +0 -0
  157. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/routers/swap.py +0 -0
  158. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/routers/transactions.py +0 -0
  159. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/server.py +0 -0
  160. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/static/app.js +0 -0
  161. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/static/index.html +0 -0
  162. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/static/style.css +0 -0
  163. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/tests/test_web_endpoints.py +0 -0
  164. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/tests/test_web_olas.py +0 -0
  165. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/tests/test_web_swap.py +0 -0
  166. {iwa-0.0.20 → iwa-0.0.23}/src/iwa/web/tests/test_web_swap_coverage.py +0 -0
  167. {iwa-0.0.20 → iwa-0.0.23}/src/iwa.egg-info/SOURCES.txt +0 -0
  168. {iwa-0.0.20 → iwa-0.0.23}/src/iwa.egg-info/dependency_links.txt +0 -0
  169. {iwa-0.0.20 → iwa-0.0.23}/src/iwa.egg-info/entry_points.txt +0 -0
  170. {iwa-0.0.20 → iwa-0.0.23}/src/iwa.egg-info/requires.txt +0 -0
  171. {iwa-0.0.20 → iwa-0.0.23}/src/iwa.egg-info/top_level.txt +0 -0
  172. {iwa-0.0.20 → iwa-0.0.23}/src/tests/legacy_cow.py +0 -0
  173. {iwa-0.0.20 → iwa-0.0.23}/src/tests/legacy_safe.py +0 -0
  174. {iwa-0.0.20 → iwa-0.0.23}/src/tests/legacy_transaction_retry_logic.py +0 -0
  175. {iwa-0.0.20 → iwa-0.0.23}/src/tests/legacy_tui.py +0 -0
  176. {iwa-0.0.20 → iwa-0.0.23}/src/tests/legacy_wallets_screen.py +0 -0
  177. {iwa-0.0.20 → iwa-0.0.23}/src/tests/legacy_web.py +0 -0
  178. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_account_service.py +0 -0
  179. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_balance_service.py +0 -0
  180. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_chain.py +0 -0
  181. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_chain_interface.py +0 -0
  182. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_chain_interface_coverage.py +0 -0
  183. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_cli.py +0 -0
  184. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_contract.py +0 -0
  185. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_db.py +0 -0
  186. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_drain_coverage.py +0 -0
  187. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_erc20.py +0 -0
  188. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_gnosis_plugin.py +0 -0
  189. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_keys.py +0 -0
  190. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_legacy_wallet.py +0 -0
  191. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_main.py +0 -0
  192. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_migration.py +0 -0
  193. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_mnemonic.py +0 -0
  194. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_modals.py +0 -0
  195. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_models.py +0 -0
  196. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_multisend.py +0 -0
  197. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_plugin_service.py +0 -0
  198. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_pricing.py +0 -0
  199. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_rate_limiter.py +0 -0
  200. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_reset_tenderly.py +0 -0
  201. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_rpc_efficiency.py +0 -0
  202. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_rpc_rotation.py +0 -0
  203. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_rpc_view.py +0 -0
  204. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_safe_coverage.py +0 -0
  205. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_safe_service.py +0 -0
  206. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_service_manager_integration.py +0 -0
  207. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_service_manager_structure.py +0 -0
  208. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_service_transaction.py +0 -0
  209. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_staking_router.py +0 -0
  210. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_staking_simple.py +0 -0
  211. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_tables.py +0 -0
  212. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_transaction_service.py +0 -0
  213. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_transfer_multisend.py +0 -0
  214. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_transfer_native.py +0 -0
  215. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_transfer_security.py +0 -0
  216. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_transfer_structure.py +0 -0
  217. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_transfer_swap_unit.py +0 -0
  218. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_ui_coverage.py +0 -0
  219. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_utils.py +0 -0
  220. {iwa-0.0.20 → iwa-0.0.23}/src/tests/test_workers.py +0 -0
  221. {iwa-0.0.20 → iwa-0.0.23}/src/tools/create_and_stake_service.py +0 -0
  222. {iwa-0.0.20 → iwa-0.0.23}/src/tools/verify_drain.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iwa
3
- Version: 0.0.20
3
+ Version: 0.0.23
4
4
  Summary: A secure, modular, and plugin-based framework for crypto agents and ops
5
5
  Requires-Python: <4.0,>=3.12
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "iwa"
3
- version = "0.0.20"
3
+ version = "0.0.23"
4
4
  description = "A secure, modular, and plugin-based framework for crypto agents and ops"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12,<4.0"
@@ -71,7 +71,7 @@ where = ["src"]
71
71
 
72
72
  [tool.ruff]
73
73
  line-length = 100
74
- target-version = "0.0.20"
74
+ target-version = "0.0.23"
75
75
  fix = true
76
76
 
77
77
  [tool.ruff.lint]
@@ -48,10 +48,17 @@ class ChainInterface:
48
48
  self._rotation_lock = threading.Lock()
49
49
  self._init_web3()
50
50
 
51
+ @property
52
+ def current_rpc(self) -> str:
53
+ """Get the current active RPC URL."""
54
+ if not self.chain.rpcs:
55
+ return ""
56
+ return self.chain.rpcs[self._current_rpc_index]
57
+
51
58
  @property
52
59
  def is_tenderly(self) -> bool:
53
60
  """Check if connected to Tenderly vNet."""
54
- rpc = self.chain.rpc or ""
61
+ rpc = self.current_rpc or ""
55
62
  return "tenderly" in rpc.lower() or "virtual" in rpc.lower()
56
63
 
57
64
  def init_block_tracking(self):
@@ -24,7 +24,7 @@ class EventMonitor:
24
24
  self.chain_interface = ChainInterfaces().get(chain_name)
25
25
  self.web3 = self.chain_interface.web3
26
26
  self.running = False
27
- if self.chain_interface.chain.rpc:
27
+ if self.chain_interface.current_rpc:
28
28
  try:
29
29
  self.last_checked_block = self.web3.eth.block_number
30
30
  except Exception:
@@ -39,7 +39,7 @@ class EventMonitor:
39
39
  f"Starting EventMonitor for {len(self.addresses)} addresses on {self.chain_interface.chain.name}"
40
40
  )
41
41
 
42
- if not self.chain_interface.chain.rpc:
42
+ if not self.chain_interface.current_rpc:
43
43
  logger.error(
44
44
  f"Cannot start EventMonitor: No RPC URL found for chain {self.chain_interface.chain.name}"
45
45
  )
@@ -102,7 +102,7 @@ class SafeService:
102
102
 
103
103
  # Use ChainInterface which has proper RPC rotation and parsing
104
104
  chain_interface = ChainInterfaces().get(chain_name)
105
- return EthereumClient(chain_interface.chain.rpc)
105
+ return EthereumClient(chain_interface.current_rpc)
106
106
 
107
107
  def _deploy_safe_contract(
108
108
  self,
@@ -254,7 +254,7 @@ class SafeService:
254
254
 
255
255
  # Use ChainInterface which has proper RPC rotation and parsing
256
256
  chain_interface = ChainInterfaces().get(chain)
257
- ethereum_client = EthereumClient(chain_interface.chain.rpc)
257
+ ethereum_client = EthereumClient(chain_interface.current_rpc)
258
258
 
259
259
  code = ethereum_client.w3.eth.get_code(account.address)
260
260
 
@@ -30,7 +30,7 @@ class SafeMultisig:
30
30
  from iwa.core.chain import ChainInterfaces
31
31
 
32
32
  chain_interface = ChainInterfaces().get(chain_name.lower())
33
- ethereum_client = EthereumClient(chain_interface.chain.rpc)
33
+ ethereum_client = EthereumClient(chain_interface.current_rpc)
34
34
  self.multisig = Safe(safe_account.address, ethereum_client)
35
35
  self.ethereum_client = ethereum_client
36
36
 
@@ -42,7 +42,7 @@ def test_init(safe_account, mock_settings, mock_safe_eth):
42
42
  """Test initialization."""
43
43
  with patch("iwa.core.chain.ChainInterfaces") as mock_ci_cls:
44
44
  mock_ci = mock_ci_cls.return_value
45
- mock_ci.get.return_value.chain.rpc = "http://rpc"
45
+ mock_ci.get.return_value.current_rpc = "http://rpc"
46
46
  ms = SafeMultisig(safe_account, "gnosis")
47
47
  assert ms.multisig is not None
48
48
  mock_safe_eth[0].assert_called_with("http://rpc") # EthereumClient init
@@ -17,6 +17,28 @@ from loguru import logger
17
17
  from iwa.core.keys import EncryptedAccount, KeyStorage
18
18
  from iwa.core.models import Config, StoredSafeAccount
19
19
 
20
+ # Known mappings from olas-operate-middleware staking programs
21
+ # See: https://github.com/valory-xyz/olas-operate-middleware/blob/main/operate/ledger/profiles.py
22
+ STAKING_PROGRAM_MAP = {
23
+ # Pearl staking programs (gnosis) - operate format
24
+ "pearl_alpha": "0x5344B7DD311e5d3DdDd46A4f71481Bd7b05AAA3e", # Expert Legacy
25
+ "pearl_beta": "0x389B46C259631Acd6a69Bde8B6cEe218230bAE8C", # Hobbyist 1 Legacy
26
+ "pearl_beta_2": "0xE56dF1E563De1B10715cB313D514af350D207212", # Expert 5 Legacy
27
+ "pearl_beta_3": "0xD7A3C8b975f71030135f1a66E9e23164d54fF455", # Expert 7 Legacy
28
+ "pearl_beta_4": "0x17dBAe44BC5618Cc254055B386A29576b4F87015", # Expert 9 Legacy
29
+ "pearl_beta_5": "0xB0ef657b8302bd2c74B6E6D9B2b4b39145b19c6f", # Expert 10 Legacy
30
+ "pearl_beta_mm_v2_1": "0x75eeca6207be98cac3fde8a20ecd7b01e50b3472", # Expert 3 MM v2
31
+ "pearl_beta_mm_v2_2": "0x9c7f6103e3a72e4d1805b9c683ea5b370ec1a99f", # Expert 4 MM v2
32
+ "pearl_beta_mm_v2_3": "0xcdC603e0Ee55Aae92519f9770f214b2Be4967f7d", # Expert 5 MM v2
33
+ # Quickstart staking programs (gnosis) - quickstart format
34
+ "quickstart_beta_expert_4": "0xaD9d891134443B443D7F30013c7e14Fe27F2E029", # Expert 4 Legacy
35
+ "quickstart_beta_expert_7": "0xD7A3C8b975f71030135f1a66E9e23164d54fF455", # Expert 7 Legacy
36
+ "quickstart_beta_expert_9": "0x17dBAe44BC5618Cc254055B386A29576b4F87015", # Expert 9 Legacy
37
+ "quickstart_beta_expert_11": "0x3112c1613eAC3dBAE3D4E38CeF023eb9E2C91CF7", # Expert 11 Legacy
38
+ "quickstart_beta_expert_16_mech_marketplace": "0x6c65430515c70a3f5E62107CC301685B7D46f991", # Expert 16 MM v1
39
+ "quickstart_beta_expert_18_mech_marketplace": "0x041e679d04Fc0D4f75Eb937Dea729Df09a58e454", # Expert 18 MM v1
40
+ }
41
+
20
42
 
21
43
  @dataclass
22
44
  class DiscoveredKey:
@@ -26,8 +48,10 @@ class DiscoveredKey:
26
48
  private_key: Optional[str] = None # Plaintext hex (None if still encrypted)
27
49
  encrypted_keystore: Optional[dict] = None # Web3 v3 keystore format
28
50
  source_file: Path = field(default_factory=Path)
29
- role: str = "unknown" # "agent", "operator", "owner"
51
+ role: str = "unknown" # "agent", "owner"
30
52
  is_encrypted: bool = False
53
+ signature_verified: bool = False
54
+ signature_failed: bool = False
31
55
 
32
56
  @property
33
57
  def is_decrypted(self) -> bool:
@@ -55,6 +79,9 @@ class DiscoveredService:
55
79
  source_folder: Path = field(default_factory=Path)
56
80
  format: str = "unknown" # "trader_runner" or "operate"
57
81
  service_name: Optional[str] = None
82
+ # New fields for full service import
83
+ staking_contract_address: Optional[str] = None
84
+ service_owner_address: Optional[str] = None
58
85
 
59
86
  @property
60
87
  def agent_key(self) -> Optional[DiscoveredKey]:
@@ -66,9 +93,14 @@ class DiscoveredService:
66
93
 
67
94
  @property
68
95
  def operator_key(self) -> Optional[DiscoveredKey]:
69
- """Get the operator key if present."""
96
+ """Get the operator (owner) key. Alias for compatibility."""
97
+ return self.owner_key
98
+
99
+ @property
100
+ def owner_key(self) -> Optional[DiscoveredKey]:
101
+ """Get the owner key if present (matches 'owner' or 'operator' roles)."""
70
102
  for key in self.keys:
71
- if key.role in ("operator", "owner"):
103
+ if key.role in ["owner", "operator"]:
72
104
  return key
73
105
  return None
74
106
 
@@ -89,15 +121,17 @@ class ImportResult:
89
121
  class OlasServiceImporter:
90
122
  """Discover and import Olas services from external directories."""
91
123
 
92
- def __init__(self, key_storage: Optional[KeyStorage] = None):
124
+ def __init__(self, key_storage: Optional[KeyStorage] = None, password: Optional[str] = None):
93
125
  """Initialize the importer.
94
126
 
95
127
  Args:
96
128
  key_storage: KeyStorage instance. If None, will create one.
129
+ password: Optional password to decrypt discovered keystores.
97
130
 
98
131
  """
99
132
  self.key_storage = key_storage or KeyStorage()
100
133
  self.config = Config()
134
+ self.password = password
101
135
 
102
136
  def scan_directory(self, path: Path) -> List[DiscoveredService]:
103
137
  """Recursively scan a directory for Olas services.
@@ -106,7 +140,7 @@ class OlasServiceImporter:
106
140
  path: Directory to scan.
107
141
 
108
142
  Returns:
109
- List of discovered services.
143
+ List of discovered services (deduplicated by chain:service_id).
110
144
 
111
145
  """
112
146
  path = Path(path)
@@ -129,8 +163,51 @@ class OlasServiceImporter:
129
163
  services = self._parse_operate_format(operate)
130
164
  discovered.extend(services)
131
165
 
132
- logger.info(f"Discovered {len(discovered)} Olas service(s)")
133
- return discovered
166
+ return self._deduplicate_services(discovered)
167
+
168
+ def _deduplicate_services(self, services: List[DiscoveredService]) -> List[DiscoveredService]:
169
+ """Deduplicate discovered services by chain:service_id."""
170
+ seen_keys: set = set()
171
+ unique_services = []
172
+ duplicates = 0
173
+ for service in services:
174
+ if service.service_id:
175
+ key = f"{service.chain_name}:{service.service_id}"
176
+ if key in seen_keys:
177
+ logger.debug(
178
+ f"Skipping duplicate service {key} from {service.source_folder}"
179
+ )
180
+ duplicates += 1
181
+ continue
182
+ seen_keys.add(key)
183
+ unique_services.append(service)
184
+
185
+ if duplicates:
186
+ logger.info(f"Skipped {duplicates} duplicate service(s)")
187
+ logger.info(f"Discovered {len(unique_services)} unique Olas service(s)")
188
+ return unique_services
189
+
190
+ def _find_trader_name(self, folder: Path) -> str:
191
+ """Find the trader name by traversing up the directory tree.
192
+
193
+ Handles quickstart format where the .operate folder is nested inside
194
+ a quickstart folder, e.g.: trader_altair/quickstart/.operate/
195
+
196
+ Returns the first folder name starting with 'trader_' or the
197
+ immediate folder name if none found.
198
+ """
199
+ current = folder
200
+ fallback = folder.name
201
+
202
+ # Traverse up looking for trader_* folder
203
+ for _ in range(5): # Max 5 levels up
204
+ if current.name.startswith("trader_"):
205
+ return current.name
206
+ current = current.parent
207
+ if current == current.parent: # Reached root
208
+ break
209
+
210
+ return fallback
134
211
 
135
212
  def _parse_trader_runner_format(self, folder: Path) -> Optional[DiscoveredService]:
136
213
  """Parse a .trader_runner folder.
@@ -153,6 +230,9 @@ class OlasServiceImporter:
153
230
  service.safe_address = self._extract_safe_address(folder)
154
231
  service.keys = self._extract_trader_keys(folder)
155
232
 
233
+ # Extract staking program from .env
234
+ self._extract_staking_from_env(service, folder)
235
+
156
236
  if not service.keys and not service.service_id:
157
237
  logger.debug(f"No valid data found in {folder}")
158
238
  return None
@@ -187,12 +267,13 @@ class OlasServiceImporter:
187
267
  if key:
188
268
  keys.append(key)
189
269
 
190
- # Parse operator_pkey.txt
270
+ # Parse operator_pkey.txt (contains owner key)
191
271
  operator_file = folder / "operator_pkey.txt"
192
272
  if operator_file.exists():
193
- key = self._parse_keystore_file(operator_file, role="operator")
273
+ key = self._parse_keystore_file(operator_file, role="owner")
194
274
  if key:
195
275
  keys.append(key)
276
+ self._verify_key_signature(key)
196
277
 
197
278
  # Also check keys.json (array of keystores)
198
279
  keys_file = folder / "keys.json"
@@ -205,6 +286,31 @@ class OlasServiceImporter:
205
286
  keys.append(key)
206
287
  return keys
207
288
 
289
+ def _extract_staking_from_env(self, service: DiscoveredService, folder: Path) -> None:
290
+ """Extract STAKING_PROGRAM from .env file in trader_runner folder."""
291
+ # Check parent folder for .env (usually alongside .trader_runner)
292
+ env_file = folder.parent / ".env"
293
+ if not env_file.exists():
294
+ # Also check inside the folder itself
295
+ env_file = folder / ".env"
296
+ if not env_file.exists():
297
+ return
298
+
299
+ try:
300
+ content = env_file.read_text()
301
+ for line in content.splitlines():
302
+ line = line.strip()
303
+ if line.startswith("STAKING_PROGRAM="):
304
+ program_id = line.split("=", 1)[1].strip().strip('"').strip("'")
305
+ if program_id:
306
+ service.staking_contract_address = self._resolve_staking_contract(
307
+ program_id, service.chain_name
308
+ )
309
+ logger.debug(f"Found STAKING_PROGRAM={program_id} in {env_file}")
310
+ break
311
+ except IOError as e:
312
+ logger.warning(f"Failed to read {env_file}: {e}")
313
+
208
314
  def _parse_operate_format(self, folder: Path) -> List[DiscoveredService]:
209
315
  """Parse a .operate folder.
210
316
 
@@ -288,12 +394,15 @@ class OlasServiceImporter:
288
394
 
289
395
  # Use the folder name containing .operate (e.g., "trader_xi")
290
396
  operate_folder = config_file.parent.parent.parent # services/<uuid> -> .operate
291
- parent_folder = operate_folder.parent # .operate -> trader_xi
397
+ parent_folder = operate_folder.parent # .operate -> trader_xi or quickstart
398
+
399
+ # Handle quickstart format: traverse up to find trader_* folder
400
+ service_name = self._find_trader_name(parent_folder)
292
401
 
293
402
  service = DiscoveredService(
294
403
  source_folder=config_file.parent,
295
404
  format="operate",
296
- service_name=parent_folder.name,
405
+ service_name=service_name,
297
406
  )
298
407
 
299
408
  # 1. Extract keys from config
@@ -311,6 +420,9 @@ class OlasServiceImporter:
311
420
  external_keys = self._extract_external_keys_folder(operate_folder)
312
421
  self._merge_unique_keys(service, external_keys)
313
422
 
423
+ # 5. Extract owner address from wallets folder
424
+ self._extract_owner_address(service, operate_folder)
425
+
314
426
  return service
315
427
 
316
428
  def _extract_keys_from_operate_config(
@@ -325,19 +437,19 @@ class OlasServiceImporter:
325
437
  # Remove 0x prefix if present
326
438
  if private_key.startswith("0x"):
327
439
  private_key = private_key[2:]
328
- keys.append(
329
- DiscoveredKey(
330
- address=key_data["address"],
331
- private_key=private_key,
332
- role="agent",
333
- source_file=config_file,
334
- is_encrypted=False,
335
- )
440
+ key = DiscoveredKey(
441
+ address=key_data["address"],
442
+ private_key=private_key,
443
+ role="agent",
444
+ source_file=config_file,
445
+ is_encrypted=False,
336
446
  )
447
+ self._verify_key_signature(key)
448
+ keys.append(key)
337
449
  return keys
338
450
 
339
451
  def _enrich_service_with_chain_info(self, service: DiscoveredService, data: dict) -> None:
340
- """Extract service ID and Safe address from chain configs."""
452
+ """Extract service ID, Safe address, and staking contract from chain configs."""
341
453
  chain_configs = data.get("chain_configs", {})
342
454
  for chain_name, chain_config in chain_configs.items():
343
455
  chain_data = chain_config.get("chain_data", {})
@@ -350,6 +462,25 @@ class OlasServiceImporter:
350
462
  if "multisig" in chain_data:
351
463
  service.safe_address = chain_data["multisig"]
352
464
 
465
+ # Extract staking contract from user_params
466
+ user_params = chain_data.get("user_params", {})
467
+ staking_program_id = user_params.get("staking_program_id")
468
+ if staking_program_id:
469
+ service.staking_contract_address = self._resolve_staking_contract(
470
+ staking_program_id, chain_name
471
+ )
472
+
473
+ def _resolve_staking_contract(
474
+ self, staking_program_id: str, chain_name: str
475
+ ) -> Optional[str]:
476
+ """Resolve a staking program ID to a contract address."""
477
+ address = STAKING_PROGRAM_MAP.get(staking_program_id)
478
+ if address:
479
+ logger.debug(f"Resolved staking program '{staking_program_id}' -> {address}")
480
+ else:
481
+ logger.warning(f"Unknown staking program ID: {staking_program_id}")
482
+ return address
483
+
353
484
  def _extract_parent_wallet_keys(self, operate_folder: Path) -> List[DiscoveredKey]:
354
485
  """Extract owner keys from parent wallets folder."""
355
486
  keys = []
@@ -357,7 +488,12 @@ class OlasServiceImporter:
357
488
  if wallets_folder.exists():
358
489
  eth_txt = wallets_folder / "ethereum.txt"
359
490
  if eth_txt.exists():
491
+ # Try plaintext first
360
492
  key = self._parse_plaintext_key_file(eth_txt, role="owner")
493
+ if not key:
494
+ # Fallback to keystore
495
+ key = self._parse_keystore_file(eth_txt, role="owner")
496
+
361
497
  if key:
362
498
  keys.append(key)
363
499
  return keys
@@ -374,6 +510,22 @@ class OlasServiceImporter:
374
510
  keys.append(key)
375
511
  return keys
376
512
 
513
+ def _extract_owner_address(self, service: DiscoveredService, operate_folder: Path) -> None:
514
+ """Extract owner address from wallets/ethereum.json."""
515
+ wallets_folder = operate_folder / "wallets"
516
+ if not wallets_folder.exists():
517
+ return
518
+
519
+ eth_json = wallets_folder / "ethereum.json"
520
+ if eth_json.exists():
521
+ try:
522
+ data = json.loads(eth_json.read_text())
523
+ if "address" in data:
524
+ service.service_owner_address = data["address"]
525
+ logger.debug(f"Extracted owner address: {service.service_owner_address}")
526
+ except (json.JSONDecodeError, IOError) as e:
527
+ logger.warning(f"Failed to parse {eth_json}: {e}")
528
+
377
529
  def _merge_unique_keys(self, service: DiscoveredService, new_keys: List[DiscoveredKey]):
378
530
  """Merge new keys into service avoiding duplicates by address."""
379
531
  existing_addrs = {k.address.lower() for k in service.keys}
@@ -398,13 +550,21 @@ class OlasServiceImporter:
398
550
  if not address.startswith("0x"):
399
551
  address = "0x" + address
400
552
 
401
- return DiscoveredKey(
553
+ key = DiscoveredKey(
402
554
  address=address,
403
555
  encrypted_keystore=keystore,
404
556
  role=role,
405
557
  source_file=file_path,
406
558
  is_encrypted=True,
407
559
  )
560
+
561
+ # Attempt decryption if password provided
562
+ if self.password:
563
+ self._attempt_decryption(key)
564
+ if key.private_key:
565
+ self._verify_key_signature(key)
566
+
567
+ return key
408
568
  except (json.JSONDecodeError, IOError) as e:
409
569
  logger.warning(f"Failed to parse keystore {file_path}: {e}")
410
570
  return None
@@ -422,15 +582,19 @@ class OlasServiceImporter:
422
582
  address = keystore.get("address", "")
423
583
  if not address.startswith("0x"):
424
584
  address = "0x" + address
425
- keys.append(
426
- DiscoveredKey(
427
- address=address,
428
- encrypted_keystore=keystore,
429
- role="agent",
430
- source_file=file_path,
431
- is_encrypted=True,
432
- )
585
+ key = DiscoveredKey(
586
+ address=address,
587
+ encrypted_keystore=keystore,
588
+ role="agent",
589
+ source_file=file_path,
590
+ is_encrypted=True,
433
591
  )
592
+ # Attempt decryption if password provided
593
+ if self.password:
594
+ self._attempt_decryption(key)
595
+ if key.private_key:
596
+ self._verify_key_signature(key)
597
+ keys.append(key)
434
598
  return keys
435
599
  except (json.JSONDecodeError, IOError):
436
600
  return []
@@ -446,13 +610,15 @@ class OlasServiceImporter:
446
610
  try:
447
611
  data = json.loads(content)
448
612
  if isinstance(data, dict) and "private_key" in data and "address" in data:
449
- return DiscoveredKey(
613
+ key = DiscoveredKey(
450
614
  address=data["address"],
451
615
  private_key=data["private_key"],
452
616
  role=role,
453
617
  source_file=file_path,
454
618
  is_encrypted=False,
455
619
  )
620
+ self._verify_key_signature(key)
621
+ return key
456
622
  except json.JSONDecodeError:
457
623
  pass
458
624
 
@@ -460,13 +626,15 @@ class OlasServiceImporter:
460
626
  if len(content) == 64 or (len(content) == 66 and content.startswith("0x")):
461
627
  private_key = content[2:] if content.startswith("0x") else content
462
628
  account = Account.from_key(bytes.fromhex(private_key))
463
- return DiscoveredKey(
629
+ key = DiscoveredKey(
464
630
  address=account.address,
465
631
  private_key=private_key,
466
632
  role=role,
467
633
  source_file=file_path,
468
634
  is_encrypted=False,
469
635
  )
636
+ self._verify_key_signature(key)
637
+ return key
470
638
 
471
639
  return None
472
640
  except Exception as e:
@@ -698,6 +866,7 @@ class OlasServiceImporter:
698
866
  def _import_service_config(self, service: DiscoveredService) -> Tuple[bool, str]:
699
867
  """Import service config to OlasConfig."""
700
868
  try:
869
+ from iwa.plugins.olas.constants import OLAS_TOKEN_ADDRESS_GNOSIS
701
870
  from iwa.plugins.olas.models import OlasConfig, Service
702
871
 
703
872
  # Get or create OlasConfig
@@ -711,13 +880,16 @@ class OlasServiceImporter:
711
880
  if key in olas_config.services:
712
881
  return False, "duplicate"
713
882
 
714
- # Create service model
883
+ # Create service model with all fields
715
884
  olas_service = Service(
716
885
  service_name=service.service_name or f"service_{service.service_id}",
717
886
  chain_name=service.chain_name,
718
887
  service_id=service.service_id,
719
- agent_ids=[], # Would need on-chain query
888
+ agent_ids=[25], # Trader agents always use agent ID 25
720
889
  multisig_address=service.safe_address,
890
+ service_owner_address=service.service_owner_address,
891
+ staking_contract_address=service.staking_contract_address,
892
+ token_address=str(OLAS_TOKEN_ADDRESS_GNOSIS),
721
893
  )
722
894
 
723
895
  # Set agent address if we have one
@@ -734,3 +906,59 @@ class OlasServiceImporter:
734
906
  return False, "Olas plugin not available"
735
907
  except Exception as e:
736
908
  return False, str(e)
909
+
910
+ def _attempt_decryption(self, key: DiscoveredKey) -> None:
911
+ """Attempt to decrypt an encrypted keystore using the provided password."""
912
+ if not self.password or not key.encrypted_keystore:
913
+ return
914
+
915
+ try:
916
+ logger.debug(f"Attempting decryption for {key.address}")
917
+
918
+ # Use Account.decrypt to handle standard web3 keystores
919
+ private_key_bytes = Account.decrypt(key.encrypted_keystore, self.password)
920
+ key.private_key = private_key_bytes.hex()
921
+ key.is_encrypted = False
922
+ # If we successfully decrypted, it's no longer "encrypted" for verification purposes
923
+ logger.debug(f"Successfully decrypted key for {key.address}")
924
+ except ValueError as e:
925
+ # Password incorrect
926
+ logger.warning(f"Decryption failed (ValueError) for {key.address}: {e}")
927
+ pass
928
+ except Exception as e:
929
+ logger.warning(f"Error decrypting key {key.address}: {type(e).__name__} - {e}")
930
+
931
+ def _verify_key_signature(self, key: DiscoveredKey) -> None:
932
+ """Verify that the plaintext private key can sign a message and recover the address."""
933
+ if not key.private_key or not key.address:
934
+ return
935
+
936
+ try:
937
+ from eth_account.messages import encode_defunct
938
+
939
+ message = "Hello, world!"
940
+ encoded_message = encode_defunct(text=message)
941
+ signed_message = Account.sign_message(encoded_message, private_key=key.private_key)
942
+ recovered_address = Account.recover_message(
943
+ encoded_message, signature=signed_message.signature
944
+ )
945
+
946
+ # Normalize address to lowercase with 0x prefix
947
+ key_addr = key.address.lower()
948
+ if not key_addr.startswith("0x"):
949
+ key_addr = "0x" + key_addr
950
+ recovered_addr = recovered_address.lower()
951
+
952
+ if recovered_addr == key_addr:
953
+ key.signature_verified = True
954
+ logger.debug(f"Signature verified for key {key.address}")
955
+ else:
956
+ key.signature_failed = True
957
+ logger.warning(
958
+ f"Signature verification FAILED for key {key.address}. "
959
+ f"Recovered: {recovered_address}"
960
+ )
961
+ except Exception as e:
962
+ key.signature_failed = True
963
+ logger.warning(f"Error verifying signature for key {key.address}: {e}")
964
+