iwa 0.0.2__tar.gz → 0.0.10__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 (212) hide show
  1. {iwa-0.0.2/src/iwa.egg-info → iwa-0.0.10}/PKG-INFO +1 -1
  2. {iwa-0.0.2 → iwa-0.0.10}/pyproject.toml +2 -2
  3. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/interface.py +30 -23
  4. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/models.py +9 -15
  5. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/contract.py +8 -2
  6. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/pricing.py +10 -8
  7. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/safe.py +13 -8
  8. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transaction.py +15 -4
  9. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/utils.py +22 -0
  10. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/safe.py +4 -3
  11. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/tests/test_safe.py +9 -7
  12. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/service.py +4 -4
  13. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/staking.py +2 -3
  14. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/plugin.py +14 -7
  15. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/lifecycle.py +109 -48
  16. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/mech.py +1 -1
  17. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/staking.py +92 -34
  18. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_plugin.py +6 -1
  19. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_plugin_full.py +12 -7
  20. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager_validation.py +16 -15
  21. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/list_contracts.py +2 -2
  22. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/dependencies.py +1 -3
  23. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/accounts.py +1 -2
  24. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/admin.py +1 -3
  25. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/funding.py +1 -3
  26. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/general.py +1 -3
  27. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/services.py +1 -2
  28. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/staking.py +19 -22
  29. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/swap.py +1 -2
  30. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/transactions.py +0 -2
  31. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/server.py +8 -6
  32. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/static/app.js +22 -0
  33. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/tests/test_web_endpoints.py +1 -1
  34. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/tests/test_web_olas.py +1 -1
  35. {iwa-0.0.2 → iwa-0.0.10/src/iwa.egg-info}/PKG-INFO +1 -1
  36. {iwa-0.0.2 → iwa-0.0.10}/src/iwa.egg-info/SOURCES.txt +1 -0
  37. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_chain_interface_coverage.py +3 -2
  38. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_contract.py +165 -0
  39. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_keys.py +2 -1
  40. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_legacy_wallet.py +11 -0
  41. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_pricing.py +32 -15
  42. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_safe_coverage.py +3 -3
  43. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_safe_service.py +3 -6
  44. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_service_transaction.py +8 -3
  45. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_staking_router.py +6 -3
  46. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transaction_service.py +4 -0
  47. iwa-0.0.10/src/tools/create_and_stake_service.py +103 -0
  48. {iwa-0.0.2 → iwa-0.0.10}/src/tools/verify_drain.py +1 -4
  49. {iwa-0.0.2 → iwa-0.0.10}/LICENSE +0 -0
  50. {iwa-0.0.2 → iwa-0.0.10}/README.md +0 -0
  51. {iwa-0.0.2 → iwa-0.0.10}/setup.cfg +0 -0
  52. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/__init__.py +0 -0
  53. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/__main__.py +0 -0
  54. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/__init__.py +0 -0
  55. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/__init__.py +0 -0
  56. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/errors.py +0 -0
  57. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/manager.py +0 -0
  58. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/rate_limiter.py +0 -0
  59. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/cli.py +0 -0
  60. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/constants.py +0 -0
  61. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/__init__.py +0 -0
  62. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/abis/erc20.json +0 -0
  63. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/abis/multisend.json +0 -0
  64. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/abis/multisend_call_only.json +0 -0
  65. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/erc20.py +0 -0
  66. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/multisend.py +0 -0
  67. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/db.py +0 -0
  68. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/ipfs.py +0 -0
  69. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/keys.py +0 -0
  70. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/mnemonic.py +0 -0
  71. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/models.py +0 -0
  72. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/monitor.py +0 -0
  73. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/plugins.py +0 -0
  74. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/secrets.py +0 -0
  75. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/__init__.py +0 -0
  76. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/account.py +0 -0
  77. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/balance.py +0 -0
  78. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/plugin.py +0 -0
  79. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/__init__.py +0 -0
  80. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/base.py +0 -0
  81. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/erc20.py +0 -0
  82. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/multisend.py +0 -0
  83. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/native.py +0 -0
  84. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/swap.py +0 -0
  85. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/tables.py +0 -0
  86. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/test.py +0 -0
  87. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/tests/test_wallet.py +0 -0
  88. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/types.py +0 -0
  89. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/ui.py +0 -0
  90. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/wallet.py +0 -0
  91. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/__init__.py +0 -0
  92. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/__init__.py +0 -0
  93. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/cow/__init__.py +0 -0
  94. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/cow/quotes.py +0 -0
  95. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/cow/swap.py +0 -0
  96. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/cow/types.py +0 -0
  97. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/cow_utils.py +0 -0
  98. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/plugin.py +0 -0
  99. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/tests/test_cow.py +0 -0
  100. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/__init__.py +0 -0
  101. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/constants.py +0 -0
  102. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/activity_checker.json +0 -0
  103. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/mech.json +0 -0
  104. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/mech_marketplace.json +0 -0
  105. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/mech_new.json +0 -0
  106. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/service_manager.json +0 -0
  107. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/service_registry.json +0 -0
  108. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/staking.json +0 -0
  109. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/staking_token.json +0 -0
  110. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/activity_checker.py +0 -0
  111. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/base.py +0 -0
  112. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/mech.py +0 -0
  113. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/mech_marketplace.py +0 -0
  114. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/importer.py +0 -0
  115. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/mech_reference.py +0 -0
  116. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/models.py +0 -0
  117. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/scripts/test_full_mech_flow.py +0 -0
  118. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/scripts/test_simple_lifecycle.py +0 -0
  119. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/__init__.py +0 -0
  120. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/base.py +0 -0
  121. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/drain.py +0 -0
  122. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/conftest.py +0 -0
  123. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_importer.py +0 -0
  124. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_importer_error_handling.py +0 -0
  125. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_mech_contracts.py +0 -0
  126. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_contracts.py +0 -0
  127. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_integration.py +0 -0
  128. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_models.py +0 -0
  129. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_view.py +0 -0
  130. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_view_actions.py +0 -0
  131. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_view_modals.py +0 -0
  132. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_lifecycle.py +0 -0
  133. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager.py +0 -0
  134. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager_errors.py +0 -0
  135. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager_flows.py +0 -0
  136. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager_mech.py +0 -0
  137. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager_rewards.py +0 -0
  138. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_staking.py +0 -0
  139. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_staking_integration.py +0 -0
  140. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_staking_validation.py +0 -0
  141. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tui/__init__.py +0 -0
  142. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tui/olas_view.py +0 -0
  143. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/__init__.py +0 -0
  144. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/check_profile.py +0 -0
  145. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/release.py +0 -0
  146. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/reset_env.py +0 -0
  147. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/reset_tenderly.py +0 -0
  148. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/restore_backup.py +0 -0
  149. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/wallet_check.py +0 -0
  150. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/__init__.py +0 -0
  151. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/app.py +0 -0
  152. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/modals/__init__.py +0 -0
  153. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/modals/base.py +0 -0
  154. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/rpc.py +0 -0
  155. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/screens/__init__.py +0 -0
  156. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/screens/wallets.py +0 -0
  157. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/tests/test_app.py +0 -0
  158. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/tests/test_rpc.py +0 -0
  159. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/tests/test_wallets_refactor.py +0 -0
  160. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/tests/test_widgets.py +0 -0
  161. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/widgets/__init__.py +0 -0
  162. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/widgets/base.py +0 -0
  163. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/workers.py +0 -0
  164. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/models.py +0 -0
  165. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/__init__.py +0 -0
  166. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/state.py +0 -0
  167. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/static/index.html +0 -0
  168. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/static/style.css +0 -0
  169. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/tests/test_web_swap.py +0 -0
  170. {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/tests/test_web_swap_coverage.py +0 -0
  171. {iwa-0.0.2 → iwa-0.0.10}/src/iwa.egg-info/dependency_links.txt +0 -0
  172. {iwa-0.0.2 → iwa-0.0.10}/src/iwa.egg-info/entry_points.txt +0 -0
  173. {iwa-0.0.2 → iwa-0.0.10}/src/iwa.egg-info/requires.txt +0 -0
  174. {iwa-0.0.2 → iwa-0.0.10}/src/iwa.egg-info/top_level.txt +0 -0
  175. {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_cow.py +0 -0
  176. {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_safe.py +0 -0
  177. {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_transaction_retry_logic.py +0 -0
  178. {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_tui.py +0 -0
  179. {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_wallets_screen.py +0 -0
  180. {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_web.py +0 -0
  181. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_account_service.py +0 -0
  182. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_balance_service.py +0 -0
  183. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_chain.py +0 -0
  184. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_chain_interface.py +0 -0
  185. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_cli.py +0 -0
  186. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_db.py +0 -0
  187. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_drain_coverage.py +0 -0
  188. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_erc20.py +0 -0
  189. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_gnosis_plugin.py +0 -0
  190. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_main.py +0 -0
  191. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_migration.py +0 -0
  192. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_mnemonic.py +0 -0
  193. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_modals.py +0 -0
  194. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_models.py +0 -0
  195. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_monitor.py +0 -0
  196. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_multisend.py +0 -0
  197. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_plugin_service.py +0 -0
  198. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_rate_limiter.py +0 -0
  199. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_reset_tenderly.py +0 -0
  200. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_rpc_view.py +0 -0
  201. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_service_manager_integration.py +0 -0
  202. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_service_manager_structure.py +0 -0
  203. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_staking_simple.py +0 -0
  204. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_tables.py +0 -0
  205. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transfer_multisend.py +0 -0
  206. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transfer_native.py +0 -0
  207. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transfer_security.py +0 -0
  208. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transfer_structure.py +0 -0
  209. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transfer_swap_unit.py +0 -0
  210. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_ui_coverage.py +0 -0
  211. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_utils.py +0 -0
  212. {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_workers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iwa
3
- Version: 0.0.2
3
+ Version: 0.0.10
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.2"
3
+ version = "0.0.10"
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.2"
74
+ target-version = "0.0.10"
75
75
  fix = true
76
76
 
77
77
  [tool.ruff.lint]
@@ -1,5 +1,6 @@
1
1
  """ChainInterface class for blockchain interactions."""
2
2
 
3
+ import threading
3
4
  import time
4
5
  from typing import Callable, Dict, Optional, Tuple, TypeVar, Union
5
6
 
@@ -45,6 +46,7 @@ class ChainInterface:
45
46
  )
46
47
 
47
48
  self._initial_block = 0
49
+ self._rotation_lock = threading.Lock()
48
50
  self._init_web3()
49
51
 
50
52
  @property
@@ -154,18 +156,6 @@ class ChainInterface:
154
156
  print("╚══════════════════════════════════════════════════╝")
155
157
  print("")
156
158
 
157
- def _init_web3(self):
158
- """Initialize Web3 with current RPC."""
159
- rpc_url = self.chain.rpcs[self._current_rpc_index] if self.chain.rpcs else ""
160
- raw_web3 = Web3(Web3.HTTPProvider(rpc_url, request_kwargs={"timeout": DEFAULT_RPC_TIMEOUT}))
161
-
162
- # Use duck typing to check if current web3 is a RateLimitedWeb3 wrapper
163
- # (isinstance check fails when RateLimitedWeb3 is mocked in tests)
164
- if hasattr(self, "web3") and hasattr(self.web3, "set_backend"):
165
- self.web3.set_backend(raw_web3)
166
- else:
167
- self.web3 = RateLimitedWeb3(raw_web3, self._rate_limiter, self)
168
-
169
159
  def _is_rate_limit_error(self, error: Exception) -> bool:
170
160
  """Check if error is a rate limit (429) error."""
171
161
  err_text = str(error).lower()
@@ -269,19 +259,36 @@ class ChainInterface:
269
259
 
270
260
  def rotate_rpc(self) -> bool:
271
261
  """Rotate to the next available RPC."""
272
- if not self.chain.rpcs or len(self.chain.rpcs) <= 1:
273
- return False
262
+ with self._rotation_lock:
263
+ if not self.chain.rpcs or len(self.chain.rpcs) <= 1:
264
+ return False
265
+
266
+ # Simple Round Robin rotation
267
+ self._current_rpc_index = (self._current_rpc_index + 1) % len(self.chain.rpcs)
268
+ # Internal call to _init_web3 already expects to be under lock if called from here,
269
+ # but _init_web3 itself doesn't have a lock. Let's make it consistent.
270
+ self._init_web3_under_lock()
271
+
272
+ logger.info(
273
+ f"Rotated RPC for {self.chain.name} to index {self._current_rpc_index}: {self.chain.rpcs[self._current_rpc_index]}"
274
+ )
275
+ return True
274
276
 
275
- # Simple Round Robin rotation
276
- # We don't check health here because the health check itself might consume rate limits
277
- # or fail flakily. Better to just switch and try the operation.
278
- self._current_rpc_index = (self._current_rpc_index + 1) % len(self.chain.rpcs)
279
- self._init_web3()
277
+ def _init_web3(self):
278
+ """Initialize Web3 with current RPC (thread-safe)."""
279
+ with self._rotation_lock:
280
+ self._init_web3_under_lock()
280
281
 
281
- logger.info(
282
- f"Rotated RPC for {self.chain.name} to index {self._current_rpc_index}: {self.chain.rpcs[self._current_rpc_index]}"
283
- )
284
- return True
282
+ def _init_web3_under_lock(self):
283
+ """Internal non-thread-safe web3 initialization."""
284
+ rpc_url = self.chain.rpcs[self._current_rpc_index] if self.chain.rpcs else ""
285
+ raw_web3 = Web3(Web3.HTTPProvider(rpc_url, request_kwargs={"timeout": DEFAULT_RPC_TIMEOUT}))
286
+
287
+ # Use duck typing to check if current web3 is a RateLimitedWeb3 wrapper
288
+ if hasattr(self, "web3") and hasattr(self.web3, "set_backend"):
289
+ self.web3.set_backend(raw_web3)
290
+ else:
291
+ self.web3 = RateLimitedWeb3(raw_web3, self._rate_limiter, self)
285
292
 
286
293
  def check_rpc_health(self) -> bool:
287
294
  """Check if the current RPC is healthy."""
@@ -78,13 +78,11 @@ class Gnosis(SupportedChain):
78
78
  if not self.rpcs and secrets.gnosis_rpc:
79
79
  self.rpcs = secrets.gnosis_rpc.get_secret_value().split(",")
80
80
 
81
- # Defensive: ensure no comma-separated strings in list
81
+ # Defensive: ensure no comma-separated strings and NO quotes in list
82
82
  new_rpcs = []
83
83
  for rpc in self.rpcs:
84
- if "," in rpc:
85
- new_rpcs.extend([r.strip() for r in rpc.split(",") if r.strip()])
86
- else:
87
- new_rpcs.append(rpc)
84
+ parts = [r.strip().strip("'\"") for r in rpc.split(",") if r.strip()]
85
+ new_rpcs.extend(parts)
88
86
  self.rpcs = new_rpcs
89
87
 
90
88
 
@@ -107,13 +105,11 @@ class Ethereum(SupportedChain):
107
105
  if not self.rpcs and secrets.ethereum_rpc:
108
106
  self.rpcs = secrets.ethereum_rpc.get_secret_value().split(",")
109
107
 
110
- # Defensive: ensure no comma-separated strings in list
108
+ # Defensive: ensure no comma-separated strings and NO quotes in list
111
109
  new_rpcs = []
112
110
  for rpc in self.rpcs:
113
- if "," in rpc:
114
- new_rpcs.extend([r.strip() for r in rpc.split(",") if r.strip()])
115
- else:
116
- new_rpcs.append(rpc)
111
+ parts = [r.strip().strip("'\"") for r in rpc.split(",") if r.strip()]
112
+ new_rpcs.extend(parts)
117
113
  self.rpcs = new_rpcs
118
114
 
119
115
 
@@ -136,13 +132,11 @@ class Base(SupportedChain):
136
132
  if not self.rpcs and secrets.base_rpc:
137
133
  self.rpcs = secrets.base_rpc.get_secret_value().split(",")
138
134
 
139
- # Defensive: ensure no comma-separated strings in list
135
+ # Defensive: ensure no comma-separated strings and NO quotes in list
140
136
  new_rpcs = []
141
137
  for rpc in self.rpcs:
142
- if "," in rpc:
143
- new_rpcs.extend([r.strip() for r in rpc.split(",") if r.strip()])
144
- else:
145
- new_rpcs.append(rpc)
138
+ parts = [r.strip().strip("'\"") for r in rpc.split(",") if r.strip()]
139
+ new_rpcs.extend(parts)
146
140
  self.rpcs = new_rpcs
147
141
 
148
142
 
@@ -190,10 +190,16 @@ class ContractInstance:
190
190
  Exception: If the call fails, with decoded error information.
191
191
 
192
192
  """
193
- method = getattr(self.contract.functions, method_name)
194
193
  try:
194
+
195
+ def do_call():
196
+ # Re-evaluate self.contract on each retry to get current provider
197
+ # This is critical for RPC rotation to work correctly
198
+ method = getattr(self.contract.functions, method_name)
199
+ return method(*args).call()
200
+
195
201
  return self.chain_interface.with_retry(
196
- lambda: method(*args).call(),
202
+ do_call,
197
203
  operation_name=f"call {method_name} on {self.name}",
198
204
  )
199
205
  except Exception as e:
@@ -9,17 +9,19 @@ from loguru import logger
9
9
 
10
10
  from iwa.core.secrets import secrets
11
11
 
12
+ # Global cache shared across all PriceService instances
13
+ _PRICE_CACHE: Dict[str, Dict] = {}
14
+ _CACHE_TTL = timedelta(minutes=30)
15
+
12
16
 
13
17
  class PriceService:
14
18
  """Service to fetch token prices from CoinGecko."""
15
19
 
16
20
  BASE_URL = "https://api.coingecko.com/api/v3"
17
21
 
18
- def __init__(self, cache_ttl_minutes: int = 5):
22
+ def __init__(self):
19
23
  """Initialize PriceService."""
20
24
  self.secrets = secrets
21
- self.cache: Dict[str, Dict] = {} # {id_currency: {"price": float, "timestamp": datetime}}
22
- self.cache_ttl = timedelta(minutes=cache_ttl_minutes)
23
25
  self.api_key = (
24
26
  self.secrets.coingecko_api_key.get_secret_value()
25
27
  if self.secrets.coingecko_api_key
@@ -39,15 +41,15 @@ class PriceService:
39
41
  """
40
42
  cache_key = f"{token_id}_{vs_currency}"
41
43
 
42
- # Check cache
43
- if cache_key in self.cache:
44
- entry = self.cache[cache_key]
45
- if datetime.now() - entry["timestamp"] < self.cache_ttl:
44
+ # Check global cache
45
+ if cache_key in _PRICE_CACHE:
46
+ entry = _PRICE_CACHE[cache_key]
47
+ if datetime.now() - entry["timestamp"] < _CACHE_TTL:
46
48
  return entry["price"]
47
49
 
48
50
  price = self._fetch_price_from_api(token_id, vs_currency)
49
51
  if price is not None:
50
- self.cache[cache_key] = {"price": price, "timestamp": datetime.now()}
52
+ _PRICE_CACHE[cache_key] = {"price": price, "timestamp": datetime.now()}
51
53
  return price
52
54
 
53
55
  def _fetch_price_from_api(self, token_id: str, vs_currency: str) -> Optional[float]:
@@ -11,7 +11,6 @@ from safe_eth.safe.safe_tx import SafeTx
11
11
  from iwa.core.constants import ZERO_ADDRESS
12
12
  from iwa.core.db import log_transaction
13
13
  from iwa.core.models import StoredSafeAccount
14
- from iwa.core.secrets import secrets
15
14
  from iwa.core.utils import (
16
15
  get_safe_master_copy_address,
17
16
  get_safe_proxy_factory_address,
@@ -99,8 +98,11 @@ class SafeService:
99
98
  return owner_addresses
100
99
 
101
100
  def _get_ethereum_client(self, chain_name: str) -> EthereumClient:
102
- rpc_secret = getattr(secrets, f"{chain_name}_rpc")
103
- return EthereumClient(rpc_secret.get_secret_value())
101
+ from iwa.core.chain import ChainInterfaces
102
+
103
+ # Use ChainInterface which has proper RPC rotation and parsing
104
+ chain_interface = ChainInterfaces().get(chain_name)
105
+ return EthereumClient(chain_interface.chain.rpc)
104
106
 
105
107
  def _deploy_safe_contract(
106
108
  self,
@@ -141,7 +143,7 @@ class SafeService:
141
143
  gas=5_000_000,
142
144
  gas_price=gas_price,
143
145
  )
144
- return tx_sent.contract_address, tx_sent.tx_hash.hex()
146
+ return tx_sent.contract_address, f"0x{tx_sent.tx_hash.hex()}"
145
147
 
146
148
  else:
147
149
  # Standard random salt via Safe.create
@@ -153,7 +155,7 @@ class SafeService:
153
155
  threshold=threshold,
154
156
  proxy_factory_address=proxy_factory_address,
155
157
  )
156
- return create_tx.contract_address, create_tx.tx_hash.hex()
158
+ return create_tx.contract_address, f"0x{create_tx.tx_hash.hex()}"
157
159
 
158
160
  def _log_safe_deployment(
159
161
  self,
@@ -248,8 +250,11 @@ class SafeService:
248
250
  continue
249
251
 
250
252
  for chain in account.chains:
251
- rpc_secret = getattr(secrets, f"{chain}_rpc")
252
- ethereum_client = EthereumClient(rpc_secret.get_secret_value())
253
+ from iwa.core.chain import ChainInterfaces
254
+
255
+ # Use ChainInterface which has proper RPC rotation and parsing
256
+ chain_interface = ChainInterfaces().get(chain)
257
+ ethereum_client = EthereumClient(chain_interface.chain.rpc)
253
258
 
254
259
  code = ethereum_client.w3.eth.get_code(account.address)
255
260
 
@@ -304,7 +309,7 @@ class SafeService:
304
309
  # Execute using the first signer
305
310
  safe_tx.execute(signer_keys[0])
306
311
 
307
- return safe_tx.tx_hash.hex()
312
+ return f"0x{safe_tx.tx_hash.hex()}"
308
313
  finally:
309
314
  # SECURITY: Overwrite keys with zeros before clearing (best effort)
310
315
  for i in range(len(signer_keys)):
@@ -30,7 +30,7 @@ class TransactionService:
30
30
  """Sign and send a transaction with retry logic for gas."""
31
31
  chain_interface = ChainInterfaces().get(chain_name)
32
32
  tx = dict(transaction)
33
- max_retries = 3
33
+ max_retries = 10
34
34
 
35
35
  if not self._prepare_transaction(tx, signer_address_or_tag, chain_interface):
36
36
  return False, {}
@@ -39,7 +39,14 @@ class TransactionService:
39
39
  try:
40
40
  signed_txn = self.key_storage.sign_transaction(tx, signer_address_or_tag)
41
41
  txn_hash = chain_interface.web3.eth.send_raw_transaction(signed_txn.raw_transaction)
42
- receipt = chain_interface.web3.eth.wait_for_transaction_receipt(txn_hash)
42
+
43
+ # Use chain_interface.with_retry for waiting for receipt to handle timeouts/RPC errors
44
+ def wait_for_receipt(tx_h=txn_hash):
45
+ return chain_interface.web3.eth.wait_for_transaction_receipt(tx_h)
46
+
47
+ receipt = chain_interface.with_retry(
48
+ wait_for_receipt, operation_name="wait_for_receipt"
49
+ )
43
50
 
44
51
  if receipt and getattr(receipt, "status", None) == 1:
45
52
  signer_account = self.account_service.resolve_account(signer_address_or_tag)
@@ -89,7 +96,9 @@ class TransactionService:
89
96
  )
90
97
  current_gas = int(tx.get("gas", 30_000))
91
98
  tx["gas"] = int(current_gas * 1.5)
92
- time.sleep(0.5 * attempt)
99
+ tx["gas"] = int(current_gas * 1.5)
100
+ # Exponential backoff for gas errors
101
+ time.sleep(min(2**attempt, 30))
93
102
  return True
94
103
  logger.exception(f"Error sending transaction: {e}")
95
104
  return False
@@ -97,9 +106,11 @@ class TransactionService:
97
106
  def _handle_generic_error(self, e, chain_interface, attempt, max_retries) -> bool:
98
107
  if attempt < max_retries:
99
108
  logger.warning(f"Error encountered: {e}. Attempting to rotate RPC...")
109
+
100
110
  if chain_interface.rotate_rpc():
101
111
  logger.info("Retrying with new RPC...")
102
- time.sleep(0.5 * attempt)
112
+ # Exponential backoff
113
+ time.sleep(min(2**attempt, 30))
103
114
  return True
104
115
  logger.exception(f"Unexpected error sending transaction: {e}")
105
116
  return False
@@ -38,24 +38,46 @@ def get_safe_proxy_factory_address(target_version: str = "1.4.1") -> str:
38
38
  raise ValueError(f"Did not find proxy factory for version {target_version}")
39
39
 
40
40
 
41
+ def get_tx_hash(receipt: dict) -> str:
42
+ """Safely extract transaction hash from receipt (handles bytes/str/None)."""
43
+ if not receipt:
44
+ return "unknown"
45
+
46
+ tx_hash = receipt.get("transactionHash", "")
47
+ if hasattr(tx_hash, "hex"):
48
+ return tx_hash.hex()
49
+ return str(tx_hash) if tx_hash else "unknown"
50
+
51
+
41
52
  def configure_logger():
42
53
  """Configure the logger for the application."""
43
54
  if hasattr(configure_logger, "configured"):
44
55
  return logger
45
56
 
57
+ import logging
58
+
46
59
  from iwa.core.constants import DATA_DIR
47
60
 
61
+ # Silence noisy third-party loggers (these use stdlib logging, not loguru)
62
+ logging.getLogger("apscheduler.scheduler").setLevel(logging.WARNING)
63
+ logging.getLogger("apscheduler.executors.default").setLevel(logging.WARNING)
64
+ logging.getLogger("httpx").setLevel(logging.WARNING)
65
+
48
66
  logger.remove()
49
67
 
50
68
  # Ensure data directory exists
51
69
  DATA_DIR.mkdir(parents=True, exist_ok=True)
52
70
 
71
+ import sys
72
+
53
73
  logger.add(
54
74
  DATA_DIR / "iwa.log",
55
75
  rotation="10 MB",
56
76
  level="INFO",
57
77
  format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}",
58
78
  )
79
+ # Restore console logging (stderr) so logs are visible in docker/systemd/frontend streams
80
+ logger.add(sys.stderr, level="INFO")
59
81
  # Also keep stderr for console if needed, but Textual captures it?
60
82
  # Textual usually captures stderr. Writing to file is safer for debugging.
61
83
  # Users previous logs show stdout format?
@@ -8,7 +8,6 @@ from safe_eth.safe import Safe, SafeOperationEnum
8
8
  from safe_eth.safe.safe_tx import SafeTx
9
9
 
10
10
  from iwa.core.models import StoredSafeAccount
11
- from iwa.core.secrets import secrets
12
11
  from iwa.core.utils import configure_logger
13
12
 
14
13
  logger = configure_logger()
@@ -28,8 +27,10 @@ class SafeMultisig:
28
27
  if chain_name.lower() not in normalized_chains:
29
28
  raise ValueError(f"Safe account is not deployed on chain: {chain_name}")
30
29
 
31
- rpc_secret = getattr(secrets, f"{chain_name.lower()}_rpc")
32
- ethereum_client = EthereumClient(rpc_secret.get_secret_value())
30
+ from iwa.core.chain import ChainInterfaces
31
+
32
+ chain_interface = ChainInterfaces().get(chain_name.lower())
33
+ ethereum_client = EthereumClient(chain_interface.chain.rpc)
33
34
  self.multisig = Safe(safe_account.address, ethereum_client)
34
35
  self.ethereum_client = ethereum_client
35
36
 
@@ -11,9 +11,8 @@ from iwa.plugins.gnosis.safe import SafeMultisig
11
11
  @pytest.fixture
12
12
  def mock_settings():
13
13
  """Mock settings."""
14
- with patch("iwa.plugins.gnosis.safe.secrets") as mock:
15
- mock.gnosis_rpc.get_secret_value.return_value = "http://rpc"
16
- yield mock
14
+ # secrets is no longer used in this module, so we don't need to patch it here
15
+ yield None
17
16
 
18
17
 
19
18
  @pytest.fixture
@@ -41,10 +40,13 @@ def safe_account():
41
40
 
42
41
  def test_init(safe_account, mock_settings, mock_safe_eth):
43
42
  """Test initialization."""
44
- ms = SafeMultisig(safe_account, "gnosis")
45
- assert ms.multisig is not None
46
- mock_safe_eth[0].assert_called_with("http://rpc") # EthereumClient init
47
- mock_safe_eth[1].assert_called() # Safe init
43
+ with patch("iwa.core.chain.ChainInterfaces") as mock_ci_cls:
44
+ mock_ci = mock_ci_cls.return_value
45
+ mock_ci.get.return_value.chain.rpc = "http://rpc"
46
+ ms = SafeMultisig(safe_account, "gnosis")
47
+ assert ms.multisig is not None
48
+ mock_safe_eth[0].assert_called_with("http://rpc") # EthereumClient init
49
+ mock_safe_eth[1].assert_called() # Safe init
48
50
 
49
51
 
50
52
  def test_init_invalid_chain(safe_account, mock_settings, mock_safe_eth):
@@ -64,10 +64,10 @@ class ServiceRegistryContract(ContractInstance):
64
64
  """Get the token address for a service."""
65
65
  return self.call("token", service_id)
66
66
 
67
- def get_agent_params(self, service_id: int, agent_id: int) -> Dict:
68
- """Get agent params (slots, bond) for a service."""
69
- (slots, bond) = self.call("getAgentParams", service_id, agent_id)
70
- return {"slots": slots, "bond": bond}
67
+ def get_agent_params(self, service_id: int) -> list:
68
+ """Get agent params (slots, bond) for all agents in a service."""
69
+ num_ids, params = self.call("getAgentParams", service_id)
70
+ return [{"slots": p[0], "bond": p[1]} for p in params]
71
71
 
72
72
  def prepare_approve_tx(
73
73
  self,
@@ -30,20 +30,19 @@ The staking contract checks that:
30
30
  4. Agent bond was deposited during service registration
31
31
  """
32
32
 
33
- import logging
34
33
  import math
35
34
  import time
36
35
  from datetime import datetime, timezone
37
36
  from enum import Enum
38
37
  from typing import Dict, List, Optional, Union
39
38
 
39
+ from loguru import logger
40
+
40
41
  from iwa.core.contracts.contract import ContractInstance
41
42
  from iwa.core.types import EthereumAddress
42
43
  from iwa.plugins.olas.contracts.activity_checker import ActivityCheckerContract
43
44
  from iwa.plugins.olas.contracts.base import OLAS_ABI_PATH
44
45
 
45
- logger = logging.getLogger(__name__)
46
-
47
46
 
48
47
  class StakingState(Enum):
49
48
  """Enum representing the staking state of a service."""
@@ -48,8 +48,12 @@ class OlasPlugin(Plugin):
48
48
  """Create a new Olas service"""
49
49
  wallet = Wallet()
50
50
  manager = ServiceManager(wallet)
51
- # Note: Manager logic currently depends on internal config state which might need setup
52
- manager.create(chain_name, owner, token, bond)
51
+ manager.create(
52
+ chain_name=chain_name,
53
+ service_owner_address_or_tag=owner,
54
+ token_address_or_tag=token,
55
+ bond_amount_wei=bond,
56
+ )
53
57
 
54
58
  def _get_safe_signers(self, safe_address: str, chain_name: str) -> tuple:
55
59
  """Query Safe signers on-chain.
@@ -65,13 +69,16 @@ class OlasPlugin(Plugin):
65
69
  from safe_eth.eth import EthereumClient
66
70
  from safe_eth.safe import Safe
67
71
 
68
- from iwa.core.secrets import secrets
72
+ from iwa.core.chain import ChainInterfaces
69
73
 
70
- rpc_secret = getattr(secrets, f"{chain_name}_rpc", None)
71
- if not rpc_secret:
72
- return None, None # Can't verify, skip
74
+ try:
75
+ chain_interface = ChainInterfaces().get(chain_name)
76
+ if not chain_interface.chain.rpcs:
77
+ return None, None
78
+ except ValueError:
79
+ return None, None # Chain not supported/configured
73
80
 
74
- ethereum_client = EthereumClient(rpc_secret.get_secret_value())
81
+ ethereum_client = EthereumClient(chain_interface.chain.rpc)
75
82
  safe = Safe(safe_address, ethereum_client)
76
83
  owners = safe.retrieve_owners()
77
84
  return owners, True