iwa 0.0.19__tar.gz → 0.0.20__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.19/src/iwa.egg-info → iwa-0.0.20}/PKG-INFO +1 -1
  2. {iwa-0.0.19 → iwa-0.0.20}/pyproject.toml +2 -2
  3. iwa-0.0.20/src/iwa/core/chainlist.py +116 -0
  4. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/constants.py +1 -0
  5. iwa-0.0.20/src/iwa/core/contracts/cache.py +131 -0
  6. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/contracts/contract.py +7 -0
  7. iwa-0.0.20/src/iwa/core/rpc_monitor.py +60 -0
  8. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/activity_checker.py +63 -25
  9. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/staking.py +115 -19
  10. iwa-0.0.20/src/iwa/plugins/olas/events.py +141 -0
  11. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/service_manager/base.py +7 -2
  12. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/service_manager/lifecycle.py +30 -5
  13. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/service_manager/mech.py +9 -0
  14. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/service_manager/staking.py +6 -2
  15. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_olas_integration.py +38 -10
  16. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_service_manager.py +7 -1
  17. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_service_manager_errors.py +22 -11
  18. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_service_manager_flows.py +24 -8
  19. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_service_staking.py +59 -15
  20. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_staking_validation.py +8 -14
  21. iwa-0.0.20/src/iwa/tools/test_chainlist.py +38 -0
  22. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/routers/olas/staking.py +9 -4
  23. {iwa-0.0.19 → iwa-0.0.20/src/iwa.egg-info}/PKG-INFO +1 -1
  24. {iwa-0.0.19 → iwa-0.0.20}/src/iwa.egg-info/SOURCES.txt +6 -0
  25. iwa-0.0.20/src/tests/test_rpc_efficiency.py +103 -0
  26. {iwa-0.0.19 → iwa-0.0.20}/LICENSE +0 -0
  27. {iwa-0.0.19 → iwa-0.0.20}/README.md +0 -0
  28. {iwa-0.0.19 → iwa-0.0.20}/setup.cfg +0 -0
  29. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/__init__.py +0 -0
  30. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/__main__.py +0 -0
  31. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/__init__.py +0 -0
  32. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/chain/__init__.py +0 -0
  33. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/chain/errors.py +0 -0
  34. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/chain/interface.py +0 -0
  35. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/chain/manager.py +0 -0
  36. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/chain/models.py +0 -0
  37. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/chain/rate_limiter.py +0 -0
  38. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/cli.py +0 -0
  39. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/contracts/__init__.py +0 -0
  40. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/contracts/abis/erc20.json +0 -0
  41. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/contracts/abis/multisend.json +0 -0
  42. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/contracts/abis/multisend_call_only.json +0 -0
  43. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/contracts/erc20.py +0 -0
  44. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/contracts/multisend.py +0 -0
  45. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/db.py +0 -0
  46. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/ipfs.py +0 -0
  47. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/keys.py +0 -0
  48. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/mnemonic.py +0 -0
  49. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/models.py +0 -0
  50. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/monitor.py +0 -0
  51. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/plugins.py +0 -0
  52. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/pricing.py +0 -0
  53. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/secrets.py +0 -0
  54. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/__init__.py +0 -0
  55. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/account.py +0 -0
  56. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/balance.py +0 -0
  57. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/plugin.py +0 -0
  58. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/safe.py +0 -0
  59. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/transaction.py +0 -0
  60. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/transfer/__init__.py +0 -0
  61. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/transfer/base.py +0 -0
  62. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/transfer/erc20.py +0 -0
  63. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/transfer/multisend.py +0 -0
  64. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/transfer/native.py +0 -0
  65. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/services/transfer/swap.py +0 -0
  66. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/tables.py +0 -0
  67. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/test.py +0 -0
  68. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/tests/test_wallet.py +0 -0
  69. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/types.py +0 -0
  70. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/ui.py +0 -0
  71. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/utils.py +0 -0
  72. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/core/wallet.py +0 -0
  73. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/__init__.py +0 -0
  74. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/gnosis/__init__.py +0 -0
  75. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/gnosis/cow/__init__.py +0 -0
  76. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/gnosis/cow/quotes.py +0 -0
  77. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/gnosis/cow/swap.py +0 -0
  78. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/gnosis/cow/types.py +0 -0
  79. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/gnosis/cow_utils.py +0 -0
  80. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/gnosis/plugin.py +0 -0
  81. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/gnosis/safe.py +0 -0
  82. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/gnosis/tests/test_cow.py +0 -0
  83. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/gnosis/tests/test_safe.py +0 -0
  84. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/__init__.py +0 -0
  85. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/constants.py +0 -0
  86. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/abis/activity_checker.json +0 -0
  87. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/abis/mech.json +0 -0
  88. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/abis/mech_marketplace.json +0 -0
  89. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/abis/mech_marketplace_v1.json +0 -0
  90. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/abis/mech_new.json +0 -0
  91. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/abis/service_manager.json +0 -0
  92. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/abis/service_registry.json +0 -0
  93. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/abis/service_registry_token_utility.json +0 -0
  94. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/abis/staking.json +0 -0
  95. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/abis/staking_token.json +0 -0
  96. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/base.py +0 -0
  97. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/mech.py +0 -0
  98. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/mech_marketplace.py +0 -0
  99. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/mech_marketplace_v1.py +0 -0
  100. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/contracts/service.py +0 -0
  101. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/importer.py +0 -0
  102. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/mech_reference.py +0 -0
  103. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/models.py +0 -0
  104. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/plugin.py +0 -0
  105. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/scripts/test_full_mech_flow.py +0 -0
  106. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/scripts/test_simple_lifecycle.py +0 -0
  107. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/service_manager/__init__.py +0 -0
  108. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/service_manager/drain.py +0 -0
  109. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/conftest.py +0 -0
  110. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_importer.py +0 -0
  111. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_importer_error_handling.py +0 -0
  112. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_mech_contracts.py +0 -0
  113. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_olas_contracts.py +0 -0
  114. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_olas_models.py +0 -0
  115. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_olas_view.py +0 -0
  116. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_olas_view_actions.py +0 -0
  117. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_olas_view_modals.py +0 -0
  118. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_plugin.py +0 -0
  119. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_plugin_full.py +0 -0
  120. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_service_lifecycle.py +0 -0
  121. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_service_manager_mech.py +0 -0
  122. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_service_manager_rewards.py +0 -0
  123. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_service_manager_validation.py +0 -0
  124. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tests/test_staking_integration.py +0 -0
  125. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tui/__init__.py +0 -0
  126. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/plugins/olas/tui/olas_view.py +0 -0
  127. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tools/__init__.py +0 -0
  128. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tools/check_profile.py +0 -0
  129. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tools/list_contracts.py +0 -0
  130. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tools/release.py +0 -0
  131. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tools/reset_env.py +0 -0
  132. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tools/reset_tenderly.py +0 -0
  133. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tools/restore_backup.py +0 -0
  134. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tools/wallet_check.py +0 -0
  135. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/__init__.py +0 -0
  136. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/app.py +0 -0
  137. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/modals/__init__.py +0 -0
  138. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/modals/base.py +0 -0
  139. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/rpc.py +0 -0
  140. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/screens/__init__.py +0 -0
  141. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/screens/wallets.py +0 -0
  142. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/tests/test_app.py +0 -0
  143. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/tests/test_rpc.py +0 -0
  144. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/tests/test_wallets_refactor.py +0 -0
  145. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/tests/test_widgets.py +0 -0
  146. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/widgets/__init__.py +0 -0
  147. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/widgets/base.py +0 -0
  148. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/tui/workers.py +0 -0
  149. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/dependencies.py +0 -0
  150. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/models.py +0 -0
  151. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/routers/accounts.py +0 -0
  152. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/routers/olas/__init__.py +0 -0
  153. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/routers/olas/admin.py +0 -0
  154. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/routers/olas/funding.py +0 -0
  155. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/routers/olas/general.py +0 -0
  156. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/routers/olas/services.py +0 -0
  157. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/routers/state.py +0 -0
  158. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/routers/swap.py +0 -0
  159. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/routers/transactions.py +0 -0
  160. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/server.py +0 -0
  161. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/static/app.js +0 -0
  162. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/static/index.html +0 -0
  163. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/static/style.css +0 -0
  164. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/tests/test_web_endpoints.py +0 -0
  165. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/tests/test_web_olas.py +0 -0
  166. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/tests/test_web_swap.py +0 -0
  167. {iwa-0.0.19 → iwa-0.0.20}/src/iwa/web/tests/test_web_swap_coverage.py +0 -0
  168. {iwa-0.0.19 → iwa-0.0.20}/src/iwa.egg-info/dependency_links.txt +0 -0
  169. {iwa-0.0.19 → iwa-0.0.20}/src/iwa.egg-info/entry_points.txt +0 -0
  170. {iwa-0.0.19 → iwa-0.0.20}/src/iwa.egg-info/requires.txt +0 -0
  171. {iwa-0.0.19 → iwa-0.0.20}/src/iwa.egg-info/top_level.txt +0 -0
  172. {iwa-0.0.19 → iwa-0.0.20}/src/tests/legacy_cow.py +0 -0
  173. {iwa-0.0.19 → iwa-0.0.20}/src/tests/legacy_safe.py +0 -0
  174. {iwa-0.0.19 → iwa-0.0.20}/src/tests/legacy_transaction_retry_logic.py +0 -0
  175. {iwa-0.0.19 → iwa-0.0.20}/src/tests/legacy_tui.py +0 -0
  176. {iwa-0.0.19 → iwa-0.0.20}/src/tests/legacy_wallets_screen.py +0 -0
  177. {iwa-0.0.19 → iwa-0.0.20}/src/tests/legacy_web.py +0 -0
  178. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_account_service.py +0 -0
  179. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_balance_service.py +0 -0
  180. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_chain.py +0 -0
  181. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_chain_interface.py +0 -0
  182. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_chain_interface_coverage.py +0 -0
  183. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_cli.py +0 -0
  184. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_contract.py +0 -0
  185. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_db.py +0 -0
  186. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_drain_coverage.py +0 -0
  187. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_erc20.py +0 -0
  188. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_gnosis_plugin.py +0 -0
  189. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_keys.py +0 -0
  190. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_legacy_wallet.py +0 -0
  191. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_main.py +0 -0
  192. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_migration.py +0 -0
  193. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_mnemonic.py +0 -0
  194. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_modals.py +0 -0
  195. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_models.py +0 -0
  196. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_monitor.py +0 -0
  197. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_multisend.py +0 -0
  198. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_plugin_service.py +0 -0
  199. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_pricing.py +0 -0
  200. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_rate_limiter.py +0 -0
  201. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_reset_tenderly.py +0 -0
  202. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_rpc_rotation.py +0 -0
  203. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_rpc_view.py +0 -0
  204. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_safe_coverage.py +0 -0
  205. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_safe_service.py +0 -0
  206. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_service_manager_integration.py +0 -0
  207. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_service_manager_structure.py +0 -0
  208. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_service_transaction.py +0 -0
  209. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_staking_router.py +0 -0
  210. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_staking_simple.py +0 -0
  211. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_tables.py +0 -0
  212. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_transaction_service.py +0 -0
  213. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_transfer_multisend.py +0 -0
  214. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_transfer_native.py +0 -0
  215. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_transfer_security.py +0 -0
  216. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_transfer_structure.py +0 -0
  217. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_transfer_swap_unit.py +0 -0
  218. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_ui_coverage.py +0 -0
  219. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_utils.py +0 -0
  220. {iwa-0.0.19 → iwa-0.0.20}/src/tests/test_workers.py +0 -0
  221. {iwa-0.0.19 → iwa-0.0.20}/src/tools/create_and_stake_service.py +0 -0
  222. {iwa-0.0.19 → iwa-0.0.20}/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.19
3
+ Version: 0.0.20
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.19"
3
+ version = "0.0.20"
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.19"
74
+ target-version = "0.0.20"
75
75
  fix = true
76
76
 
77
77
  [tool.ruff.lint]
@@ -0,0 +1,116 @@
1
+ """Module for fetching and parsing RPCs from Chainlist.org."""
2
+ import json
3
+ import time
4
+ from dataclasses import dataclass
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ import requests
8
+
9
+ from iwa.core.constants import CACHE_DIR
10
+
11
+
12
+ @dataclass
13
+ class RPCNode:
14
+ """Represents a single RPC node with its properties."""
15
+
16
+ url: str
17
+ is_working: bool
18
+ privacy: Optional[str] = None
19
+ tracking: Optional[str] = None
20
+
21
+ @property
22
+ def is_tracking(self) -> bool:
23
+ """Returns True if the RPC is known to track user data."""
24
+ return self.privacy == "privacy" or self.tracking in ("limited", "yes")
25
+
26
+
27
+ class ChainlistRPC:
28
+ """Fetcher and parser for Chainlist RPC data."""
29
+
30
+ URL = "https://chainlist.org/rpcs.json"
31
+ CACHE_PATH = CACHE_DIR / "chainlist_rpcs.json"
32
+ CACHE_TTL = 86400 # 24 hours
33
+
34
+ def __init__(self) -> None:
35
+ """Initialize the ChainlistRPC instance."""
36
+ self._data: List[Dict[str, Any]] = []
37
+
38
+ def fetch_data(self, force_refresh: bool = False) -> None:
39
+ """Fetches the RPC data from Chainlist with local caching."""
40
+ # 1. Try local cache first unless force_refresh is requested
41
+ if not force_refresh and self.CACHE_PATH.exists():
42
+ try:
43
+ mtime = self.CACHE_PATH.stat().st_mtime
44
+ if time.time() - mtime < self.CACHE_TTL:
45
+ with self.CACHE_PATH.open("r") as f:
46
+ self._data = json.load(f)
47
+ if self._data:
48
+ return
49
+ except Exception as e:
50
+ print(f"Error reading Chainlist cache: {e}")
51
+
52
+ # 2. Fetch from remote
53
+ try:
54
+ response = requests.get(self.URL, timeout=10)
55
+ response.raise_for_status()
56
+ self._data = response.json()
57
+
58
+ # 3. Update local cache
59
+ if self._data:
60
+ self.CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
61
+ with self.CACHE_PATH.open("w") as f:
62
+ json.dump(self._data, f)
63
+ except requests.RequestException as e:
64
+ print(f"Error fetching Chainlist data from {self.URL}: {e}")
65
+ # Fallback to expired cache if available
66
+ if not self._data and self.CACHE_PATH.exists():
67
+ try:
68
+ with self.CACHE_PATH.open("r") as f:
69
+ self._data = json.load(f)
70
+ except Exception:
71
+ pass
72
+ if not self._data:
73
+ self._data = []
74
+
75
+ def get_chain_data(self, chain_id: int) -> Optional[Dict[str, Any]]:
76
+ """Returns the raw chain data for a specific chain ID."""
77
+ if not self._data:
78
+ self.fetch_data()
79
+
80
+ for entry in self._data:
81
+ if entry.get('chainId') == chain_id:
82
+ return entry
83
+ return None
84
+
85
+ def get_rpcs(self, chain_id: int) -> List[RPCNode]:
86
+ """Returns a list of RPCNode objects for a parsed and cleaner view."""
87
+ chain_data = self.get_chain_data(chain_id)
88
+ if not chain_data:
89
+ return []
90
+
91
+ raw_rpcs = chain_data.get('rpc', [])
92
+ nodes = []
93
+ for rpc in raw_rpcs:
94
+ nodes.append(RPCNode(
95
+ url=rpc.get('url', ''),
96
+ is_working=True,
97
+ privacy=rpc.get('privacy'),
98
+ tracking=rpc.get('tracking')
99
+ ))
100
+ return nodes
101
+
102
+ def get_https_rpcs(self, chain_id: int) -> List[str]:
103
+ """Returns a list of HTTPS RPC URLs for the given chain."""
104
+ rpcs = self.get_rpcs(chain_id)
105
+ return [
106
+ node.url for node in rpcs
107
+ if node.url.startswith("https://") or node.url.startswith("http://")
108
+ ]
109
+
110
+ def get_wss_rpcs(self, chain_id: int) -> List[str]:
111
+ """Returns a list of WSS RPC URLs for the given chain."""
112
+ rpcs = self.get_rpcs(chain_id)
113
+ return [
114
+ node.url for node in rpcs
115
+ if node.url.startswith("wss://") or node.url.startswith("ws://")
116
+ ]
@@ -8,6 +8,7 @@ PROJECT_ROOT = Path(__file__).parent.parent.parent.parent
8
8
 
9
9
  # Data directory for runtime files
10
10
  DATA_DIR = Path("data")
11
+ CACHE_DIR = DATA_DIR / "cache"
11
12
 
12
13
  # secrets.env is at project root (NOT in data/)
13
14
  SECRETS_PATH = Path("secrets.env")
@@ -0,0 +1,131 @@
1
+ """Contract instance cache to reduce RPC calls during instantiation."""
2
+
3
+ import os
4
+ import time
5
+ from threading import Lock
6
+ from typing import Any, Dict, Optional, Type, TypeVar
7
+
8
+ from loguru import logger
9
+
10
+ T = TypeVar("T")
11
+
12
+
13
+ class ContractCache:
14
+ """Singleton cache for contract instances.
15
+
16
+ Stores contract instances keyed by (class, address, chain) to prevent
17
+ redundant instantiation and the associated RPC calls.
18
+ """
19
+
20
+ _instance = None
21
+ _lock = Lock()
22
+
23
+ def __new__(cls) -> "ContractCache":
24
+ """Ensure singleton instance."""
25
+ with cls._lock:
26
+ if cls._instance is None:
27
+ cls._instance = super(ContractCache, cls).__new__(cls)
28
+ cls._instance._contracts: Dict[str, Any] = {}
29
+ cls._instance._creation_times: Dict[str, float] = {}
30
+
31
+ # Default TTL: 1 hour, configurable via env var
32
+ env_ttl = os.environ.get("IWA_CONTRACT_CACHE_TTL")
33
+ try:
34
+ cls._instance.ttl = int(env_ttl) if env_ttl else 3600
35
+ except ValueError:
36
+ cls._instance.ttl = 3600
37
+ logger.warning(f"Invalid IWA_CONTRACT_CACHE_TTL value: {env_ttl}. Using 3600.")
38
+
39
+ return cls._instance
40
+
41
+ def get_contract(
42
+ self,
43
+ contract_cls: Type[T],
44
+ address: str,
45
+ chain_name: str,
46
+ ttl: Optional[int] = None,
47
+ ) -> T:
48
+ """Get a cached contract instance or create a new one.
49
+
50
+ Args:
51
+ contract_cls: The contract class to instantiate.
52
+ address: The contract address.
53
+ chain_name: The chain name.
54
+ ttl: Optional TTL override in seconds.
55
+
56
+ Returns:
57
+ The contract instance (cached or new).
58
+
59
+ """
60
+ if not address:
61
+ raise ValueError("Address is required for contract caching")
62
+
63
+ key = self._make_key(contract_cls, address, chain_name)
64
+ now = time.time()
65
+ expiry = (ttl if ttl is not None else self.ttl)
66
+
67
+ with self._lock:
68
+ # Check if cached and valid
69
+ if key in self._contracts:
70
+ created_at = self._creation_times.get(key, 0)
71
+ if now - created_at < expiry:
72
+ return self._contracts[key]
73
+ else:
74
+ logger.debug(f"Contract cache expired for {key}")
75
+ del self._contracts[key]
76
+ del self._creation_times[key]
77
+
78
+ # Create new instance
79
+ logger.debug(f"Creating new cached contract instance for {key}")
80
+ instance = contract_cls(address, chain_name=chain_name)
81
+ self._contracts[key] = instance
82
+ self._creation_times[key] = now
83
+ return instance
84
+
85
+ def get_if_cached(
86
+ self,
87
+ contract_cls: Type[T],
88
+ address: str,
89
+ chain_name: str,
90
+ ) -> Optional[T]:
91
+ """Get a cached contract instance if it exists and is valid.
92
+
93
+ Does NOT create a new instance if not found.
94
+ """
95
+ if not address:
96
+ return None
97
+
98
+ key = self._make_key(contract_cls, address, chain_name)
99
+ now = time.time()
100
+
101
+ with self._lock:
102
+ if key in self._contracts:
103
+ # Check TTL
104
+ created_at = self._creation_times.get(key, 0)
105
+ if now - created_at < self.ttl:
106
+ return self._contracts[key]
107
+ else:
108
+ # Expired, clean up
109
+ del self._contracts[key]
110
+ del self._creation_times[key]
111
+ return None
112
+
113
+ def _make_key(self, contract_cls: Type, address: str, chain_name: str) -> str:
114
+ """Create a unique cache key."""
115
+ return f"{contract_cls.__name__}:{chain_name.lower()}:{address.lower()}"
116
+
117
+ def clear(self) -> None:
118
+ """Clear all cached contracts."""
119
+ with self._lock:
120
+ self._contracts.clear()
121
+ self._creation_times.clear()
122
+ logger.debug("Contract cache cleared")
123
+
124
+ def invalidate(self, contract_cls: Type, address: str, chain_name: str) -> None:
125
+ """Invalidate a specific contract in the cache."""
126
+ key = self._make_key(contract_cls, address, chain_name)
127
+ with self._lock:
128
+ if key in self._contracts:
129
+ del self._contracts[key]
130
+ del self._creation_times[key]
131
+ logger.debug(f"Invalidated cache for {key}")
@@ -11,6 +11,7 @@ from web3.contract import Contract
11
11
  from web3.exceptions import ContractCustomError
12
12
 
13
13
  from iwa.core.chain import ChainInterfaces
14
+ from iwa.core.rpc_monitor import RPCMonitor
14
15
  from iwa.core.utils import configure_logger
15
16
 
16
17
  logger = configure_logger()
@@ -221,6 +222,8 @@ class ContractInstance:
221
222
  # Re-evaluate self.contract on each retry to get current provider
222
223
  # This is critical for RPC rotation to work correctly
223
224
  method = getattr(self.contract.functions, method_name)
225
+ # Count the RPC call
226
+ RPCMonitor().increment(f"{self.name}.{method_name}")
224
227
  return method(*args).call()
225
228
 
226
229
  return self.chain_interface.with_retry(
@@ -277,6 +280,10 @@ class ContractInstance:
277
280
 
278
281
  try:
279
282
  tx_params = self.chain_interface.calculate_transaction_params(built_method, tx_params)
283
+
284
+ # Count the estimateGas/buildTransaction RPC calls
285
+ RPCMonitor().increment(f"{self.name}.{method_name}.estimate_gas")
286
+
280
287
  transaction = built_method.build_transaction(tx_params)
281
288
  return transaction
282
289
 
@@ -0,0 +1,60 @@
1
+ """RPC Monitor for tracking API usage."""
2
+ import threading
3
+ from collections import defaultdict
4
+ from typing import Dict
5
+
6
+ from iwa.core.utils import configure_logger
7
+
8
+ logger = configure_logger()
9
+
10
+
11
+ class RPCMonitor:
12
+ """Singleton monitor for tracking RPC usage."""
13
+
14
+ _instance = None
15
+ _lock = threading.Lock()
16
+
17
+ def __new__(cls):
18
+ """Create singleton instance."""
19
+ if cls._instance is None:
20
+ with cls._lock:
21
+ if cls._instance is None:
22
+ cls._instance = super(RPCMonitor, cls).__new__(cls)
23
+ cls._instance._initialized = False
24
+ return cls._instance
25
+
26
+ def __init__(self):
27
+ """Initialize monitor."""
28
+ if self._initialized:
29
+ return
30
+ self._counts: Dict[str, int] = defaultdict(int)
31
+ self._lock = threading.Lock()
32
+ self._initialized = True
33
+
34
+ def increment(self, metric_name: str, count: int = 1):
35
+ """Increment a metric counter."""
36
+ with self._lock:
37
+ self._counts[metric_name] += count
38
+
39
+ def get_counts(self) -> Dict[str, int]:
40
+ """Get a copy of current counts."""
41
+ with self._lock:
42
+ return dict(self._counts)
43
+
44
+ def log_stats(self):
45
+ """Log current statistics."""
46
+ stats = self.get_counts()
47
+ if not stats:
48
+ return
49
+
50
+ logger.info("RPC Stats Summary:")
51
+ total = 0
52
+ for k, v in sorted(stats.items()):
53
+ logger.info(f" {k}: {v}")
54
+ total += v
55
+ logger.info(f" TOTAL: {total}")
56
+
57
+ def clear(self):
58
+ """Clear all counters."""
59
+ with self._lock:
60
+ self._counts.clear()
@@ -8,7 +8,7 @@ The liveness check (isRatioPass) verifies that the service is making enough mech
8
8
  requests relative to the time elapsed since the last checkpoint.
9
9
  """
10
10
 
11
- from typing import Tuple
11
+ from typing import Optional, Tuple
12
12
 
13
13
  from iwa.core.constants import DEFAULT_MECH_CONTRACT_ADDRESS
14
14
  from iwa.core.types import EthereumAddress
@@ -43,27 +43,11 @@ class ActivityCheckerContract(ContractInstance):
43
43
  """
44
44
  super().__init__(address, chain_name=chain_name)
45
45
 
46
- # Check for marketplace-aware checker
47
- try:
48
- mech_mp_function = getattr(self.contract.functions, "mechMarketplace", None)
49
- self.mech_marketplace = mech_mp_function().call() if mech_mp_function else None
50
- except Exception:
51
- self.mech_marketplace = None
52
-
53
- # Get the mech address this checker tracks (legacy or priority mech)
54
- try:
55
- agent_mech_function = getattr(self.contract.functions, "agentMech", None)
56
- self.agent_mech = (
57
- agent_mech_function().call() if agent_mech_function else DEFAULT_MECH_CONTRACT_ADDRESS
58
- )
59
- except Exception:
60
- self.agent_mech = DEFAULT_MECH_CONTRACT_ADDRESS
61
-
62
- # Get liveness ratio (requests per second * 1e18)
63
- try:
64
- self.liveness_ratio = self.contract.functions.livenessRatio().call()
65
- except Exception:
66
- self.liveness_ratio = 0
46
+ # Cache for lazy loading
47
+ self._mech_marketplace: Optional[EthereumAddress] = None
48
+ self._agent_mech: Optional[EthereumAddress] = None
49
+ self._liveness_ratio: Optional[int] = None
50
+
67
51
 
68
52
  def get_multisig_nonces(self, multisig: EthereumAddress) -> Tuple[int, int]:
69
53
  """Get the nonces for a multisig address.
@@ -80,6 +64,41 @@ class ActivityCheckerContract(ContractInstance):
80
64
  nonces = self.contract.functions.getMultisigNonces(multisig).call()
81
65
  return (nonces[0], nonces[1])
82
66
 
67
+
68
+ @property
69
+ def mech_marketplace(self) -> Optional[EthereumAddress]:
70
+ """Get the mech marketplace address."""
71
+ if self._mech_marketplace is None:
72
+ try:
73
+ mech_mp_function = getattr(self.contract.functions, "mechMarketplace", None)
74
+ self._mech_marketplace = mech_mp_function().call() if mech_mp_function else None
75
+ except Exception:
76
+ self._mech_marketplace = None
77
+ return self._mech_marketplace
78
+
79
+ @property
80
+ def agent_mech(self) -> EthereumAddress:
81
+ """Get the agent mech address."""
82
+ if self._agent_mech is None:
83
+ try:
84
+ agent_mech_function = getattr(self.contract.functions, "agentMech", None)
85
+ self._agent_mech = (
86
+ agent_mech_function().call() if agent_mech_function else DEFAULT_MECH_CONTRACT_ADDRESS
87
+ )
88
+ except Exception:
89
+ self._agent_mech = DEFAULT_MECH_CONTRACT_ADDRESS
90
+ return self._agent_mech
91
+
92
+ @property
93
+ def liveness_ratio(self) -> int:
94
+ """Get the liveness ratio."""
95
+ if self._liveness_ratio is None:
96
+ try:
97
+ self._liveness_ratio = self.contract.functions.livenessRatio().call()
98
+ except Exception:
99
+ self._liveness_ratio = 0
100
+ return self._liveness_ratio
101
+
83
102
  def is_ratio_pass(
84
103
  self,
85
104
  current_nonces: Tuple[int, int],
@@ -101,6 +120,25 @@ class ActivityCheckerContract(ContractInstance):
101
120
  True if liveness requirements are met.
102
121
 
103
122
  """
104
- return self.contract.functions.isRatioPass(
105
- list(current_nonces), list(last_nonces), ts_diff
106
- ).call()
123
+ # Optimized implementation to avoid RPC call
124
+ current_safe, current_requests = current_nonces
125
+ last_safe, last_requests = last_nonces
126
+
127
+ diff_safe = current_safe - last_safe
128
+ diff_requests = current_requests - last_requests
129
+
130
+ # 1. Check if requests exceed transactions (impossible in valid operation)
131
+ # Also check for negative diffs (data corruption/stale data edge case)
132
+ if diff_requests > diff_safe or diff_requests < 0 or diff_safe < 0:
133
+ return False
134
+
135
+ # 2. Check time difference validity
136
+ if ts_diff == 0:
137
+ return False
138
+
139
+ # 3. Check ratio
140
+ # ratio = (diffRequests * 1e18) / ts_diff >= livenessRatio
141
+ # We use integer arithmetic as per Solidity
142
+ ratio = (diff_requests * 10**18) // ts_diff
143
+
144
+ return ratio >= self.liveness_ratio
@@ -79,22 +79,9 @@ class StakingContract(ContractInstance):
79
79
  self.chain_name = chain_name
80
80
  self._contract_params_cache: Dict[str, int] = {}
81
81
 
82
- # Get activity checker from the staking contract
83
- activity_checker_address = self.call("activityChecker")
84
- self.activity_checker = ActivityCheckerContract(
85
- activity_checker_address, chain_name=chain_name
86
- )
87
- self.activity_checker_address = activity_checker_address
88
-
89
- # Cache contract parameters
90
- self.available_rewards = self.call("availableRewards")
91
- self.balance = self.call("balance")
92
- self.liveness_period = self.call("livenessPeriod")
93
- self.rewards_per_second = self.call("rewardsPerSecond")
94
- self.max_num_services = self.call("maxNumServices")
95
- self.min_staking_deposit = self.call("minStakingDeposit")
96
- self.min_staking_duration_hours = self.call("minStakingDuration") / 3600
97
- self.staking_token_address = self.call("stakingToken")
82
+ self._activity_checker: Optional[ActivityCheckerContract] = None
83
+ self._activity_checker_address: Optional[EthereumAddress] = None
84
+
98
85
 
99
86
  def get_requirements(self) -> Dict[str, Union[str, int]]:
100
87
  """Get the contract requirements for token and deposits.
@@ -193,10 +180,16 @@ class StakingContract(ContractInstance):
193
180
  remaining_seconds = (epoch_end - datetime.now(timezone.utc)).total_seconds()
194
181
 
195
182
  # Check liveness ratio using activity checker
183
+ # logic: use the latest of (service_start_time, global_checkpoint_time)
184
+ # If service started AFTER global checkpoint, use service_start_time.
185
+ # If service was already running, use global_checkpoint_time.
186
+ global_ts_checkpoint = self.ts_checkpoint()
187
+ effective_ts_start = max(ts_start, global_ts_checkpoint)
188
+
196
189
  liveness_passed = self.is_liveness_ratio_passed(
197
190
  current_nonces=current_nonces,
198
191
  last_nonces=(last_safe_nonce, last_mech_requests),
199
- ts_start=ts_start,
192
+ ts_start=effective_ts_start,
200
193
  )
201
194
 
202
195
  return {
@@ -223,8 +216,36 @@ class StakingContract(ContractInstance):
223
216
  return StakingState(self.call("getStakingState", service_id))
224
217
 
225
218
  def ts_checkpoint(self) -> int:
226
- """Get the timestamp of the last checkpoint."""
227
- return self.call("tsCheckpoint")
219
+ """Get the timestamp of the last checkpoint.
220
+
221
+ Cached until the estimated end of the current epoch (ts_checkpoint + liveness_period).
222
+ """
223
+ now = time.time()
224
+ cache_key = "ts_checkpoint"
225
+
226
+ # Check if we have a valid cached value
227
+ if cache_key in self._contract_params_cache:
228
+ ts = self._contract_params_cache[cache_key]
229
+ # Use liveness period to determine if we should re-check
230
+ if now < ts + self.liveness_period:
231
+ return ts
232
+
233
+ # If past expected epoch end, check at most once per minute
234
+ last_checked = self._contract_params_cache.get(f"{cache_key}_last_checked", 0)
235
+ if now - last_checked < 60:
236
+ return ts
237
+
238
+ # Fetch new value
239
+ ts = self.call("tsCheckpoint")
240
+ self._contract_params_cache[cache_key] = ts
241
+ self._contract_params_cache[f"{cache_key}_last_checked"] = now
242
+ return ts
243
+
244
+ def clear_epoch_cache(self) -> None:
245
+ """Clear cache for epoch-dependent properties."""
246
+ self._contract_params_cache.pop("ts_checkpoint", None)
247
+ self._contract_params_cache.pop("ts_checkpoint_last_checked", None)
248
+ logger.debug(f"Cleared epoch cache for StakingContract {self.address}")
228
249
 
229
250
  def get_required_requests(self, use_liveness_period: bool = True) -> int:
230
251
  """Calculate the required requests for the current epoch.
@@ -246,6 +267,81 @@ class StakingContract(ContractInstance):
246
267
  (time_diff * self.activity_checker.liveness_ratio) / 1e18 + requests_safety_margin
247
268
  )
248
269
 
270
+ @property
271
+ def activity_checker_address_value(self) -> EthereumAddress:
272
+ """Get the activity checker address."""
273
+ if self._activity_checker_address is None:
274
+ self._activity_checker_address = self.call("activityChecker")
275
+ return self._activity_checker_address
276
+
277
+ @property
278
+ def activity_checker_address(self) -> EthereumAddress:
279
+ """Backwards compatibility for activity_checker_address."""
280
+ return self.activity_checker_address_value
281
+
282
+ @property
283
+ def activity_checker(self) -> ActivityCheckerContract:
284
+ """Get the activity checker contract."""
285
+ if self._activity_checker is None:
286
+ self._activity_checker = ActivityCheckerContract(
287
+ self.activity_checker_address_value, chain_name=self.chain_name
288
+ )
289
+ return self._activity_checker
290
+
291
+ @property
292
+ def available_rewards(self) -> int:
293
+ """Get available rewards."""
294
+ if "availableRewards" not in self._contract_params_cache:
295
+ self._contract_params_cache["availableRewards"] = self.call("availableRewards")
296
+ return self._contract_params_cache["availableRewards"]
297
+
298
+ @property
299
+ def balance(self) -> int:
300
+ """Get contract balance."""
301
+ if "balance" not in self._contract_params_cache:
302
+ self._contract_params_cache["balance"] = self.call("balance")
303
+ return self._contract_params_cache["balance"]
304
+
305
+ @property
306
+ def liveness_period(self) -> int:
307
+ """Get liveness period."""
308
+ if "livenessPeriod" not in self._contract_params_cache:
309
+ self._contract_params_cache["livenessPeriod"] = self.call("livenessPeriod")
310
+ return self._contract_params_cache["livenessPeriod"]
311
+
312
+ @property
313
+ def rewards_per_second(self) -> int:
314
+ """Get rewards per second."""
315
+ if "rewardsPerSecond" not in self._contract_params_cache:
316
+ self._contract_params_cache["rewardsPerSecond"] = self.call("rewardsPerSecond")
317
+ return self._contract_params_cache["rewardsPerSecond"]
318
+
319
+ @property
320
+ def max_num_services(self) -> int:
321
+ """Get max number of services."""
322
+ if "maxNumServices" not in self._contract_params_cache:
323
+ self._contract_params_cache["maxNumServices"] = self.call("maxNumServices")
324
+ return self._contract_params_cache["maxNumServices"]
325
+
326
+ @property
327
+ def min_staking_deposit(self) -> int:
328
+ """Get min staking deposit."""
329
+ if "minStakingDeposit" not in self._contract_params_cache:
330
+ self._contract_params_cache["minStakingDeposit"] = self.call("minStakingDeposit")
331
+ return self._contract_params_cache["minStakingDeposit"]
332
+
333
+ @property
334
+ def min_staking_duration_hours(self) -> float:
335
+ """Get min staking duration in hours."""
336
+ return self.min_staking_duration / 3600
337
+
338
+ @property
339
+ def staking_token_address(self) -> EthereumAddress:
340
+ """Get staking token address."""
341
+ if "stakingToken" not in self._contract_params_cache:
342
+ self._contract_params_cache["stakingToken"] = self.call("stakingToken")
343
+ return self._contract_params_cache["stakingToken"]
344
+
249
345
  def is_liveness_ratio_passed(
250
346
  self,
251
347
  current_nonces: tuple,