iwa 0.0.61__tar.gz → 0.0.62__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 (235) hide show
  1. {iwa-0.0.61/src/iwa.egg-info → iwa-0.0.62}/PKG-INFO +1 -1
  2. {iwa-0.0.61 → iwa-0.0.62}/pyproject.toml +2 -2
  3. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/interface.py +26 -12
  4. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/safe_executor.py +110 -26
  5. {iwa-0.0.61 → iwa-0.0.62/src/iwa.egg-info}/PKG-INFO +1 -1
  6. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_chainlist_enrichment.py +2 -2
  7. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_safe_executor.py +70 -0
  8. {iwa-0.0.61 → iwa-0.0.62}/LICENSE +0 -0
  9. {iwa-0.0.61 → iwa-0.0.62}/README.md +0 -0
  10. {iwa-0.0.61 → iwa-0.0.62}/setup.cfg +0 -0
  11. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/__init__.py +0 -0
  12. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/__main__.py +0 -0
  13. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/__init__.py +0 -0
  14. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/__init__.py +0 -0
  15. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/errors.py +0 -0
  16. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/manager.py +0 -0
  17. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/models.py +0 -0
  18. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/rate_limiter.py +0 -0
  19. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chainlist.py +0 -0
  20. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/cli.py +0 -0
  21. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/constants.py +0 -0
  22. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/__init__.py +0 -0
  23. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/abis/erc20.json +0 -0
  24. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/abis/multisend.json +0 -0
  25. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/abis/multisend_call_only.json +0 -0
  26. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/cache.py +0 -0
  27. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/contract.py +0 -0
  28. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/decoder.py +0 -0
  29. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/erc20.py +0 -0
  30. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/multisend.py +0 -0
  31. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/db.py +0 -0
  32. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/http.py +0 -0
  33. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/ipfs.py +0 -0
  34. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/keys.py +0 -0
  35. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/mnemonic.py +0 -0
  36. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/models.py +0 -0
  37. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/monitor.py +0 -0
  38. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/plugins.py +0 -0
  39. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/pricing.py +0 -0
  40. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/rpc_monitor.py +0 -0
  41. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/secrets.py +0 -0
  42. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/__init__.py +0 -0
  43. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/account.py +0 -0
  44. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/balance.py +0 -0
  45. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/plugin.py +0 -0
  46. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/safe.py +0 -0
  47. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transaction.py +0 -0
  48. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/__init__.py +0 -0
  49. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/base.py +0 -0
  50. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/erc20.py +0 -0
  51. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/multisend.py +0 -0
  52. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/native.py +0 -0
  53. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/swap.py +0 -0
  54. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tables.py +0 -0
  55. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/test.py +0 -0
  56. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tests/test_gnosis_fee.py +0 -0
  57. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tests/test_ipfs.py +0 -0
  58. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tests/test_pricing.py +0 -0
  59. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tests/test_regression_fixes.py +0 -0
  60. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tests/test_wallet.py +0 -0
  61. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/types.py +0 -0
  62. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/ui.py +0 -0
  63. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/utils.py +0 -0
  64. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/wallet.py +0 -0
  65. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/__init__.py +0 -0
  66. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/__init__.py +0 -0
  67. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/cow/__init__.py +0 -0
  68. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/cow/quotes.py +0 -0
  69. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/cow/swap.py +0 -0
  70. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/cow/types.py +0 -0
  71. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/cow_utils.py +0 -0
  72. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/plugin.py +0 -0
  73. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/safe.py +0 -0
  74. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/tests/test_cow.py +0 -0
  75. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/tests/test_safe.py +0 -0
  76. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/__init__.py +0 -0
  77. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/constants.py +0 -0
  78. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/activity_checker.json +0 -0
  79. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/mech.json +0 -0
  80. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/mech_marketplace.json +0 -0
  81. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/mech_marketplace_v1.json +0 -0
  82. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/mech_new.json +0 -0
  83. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/service_manager.json +0 -0
  84. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/service_registry.json +0 -0
  85. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/service_registry_token_utility.json +0 -0
  86. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/staking.json +0 -0
  87. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/staking_token.json +0 -0
  88. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/activity_checker.py +0 -0
  89. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/base.py +0 -0
  90. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/mech.py +0 -0
  91. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/mech_marketplace.py +0 -0
  92. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/mech_marketplace_v1.py +0 -0
  93. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/service.py +0 -0
  94. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/staking.py +0 -0
  95. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/events.py +0 -0
  96. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/importer.py +0 -0
  97. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/mech_reference.py +0 -0
  98. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/models.py +0 -0
  99. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/plugin.py +0 -0
  100. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/scripts/test_full_mech_flow.py +0 -0
  101. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/scripts/test_simple_lifecycle.py +0 -0
  102. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/__init__.py +0 -0
  103. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/base.py +0 -0
  104. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/drain.py +0 -0
  105. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/lifecycle.py +0 -0
  106. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/mech.py +0 -0
  107. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/staking.py +0 -0
  108. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/conftest.py +0 -0
  109. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_importer.py +0 -0
  110. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_importer_error_handling.py +0 -0
  111. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_mech_contracts.py +0 -0
  112. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_archiving.py +0 -0
  113. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_contracts.py +0 -0
  114. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_integration.py +0 -0
  115. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_models.py +0 -0
  116. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_view.py +0 -0
  117. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_view_actions.py +0 -0
  118. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_view_modals.py +0 -0
  119. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_plugin.py +0 -0
  120. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_plugin_full.py +0 -0
  121. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_lifecycle.py +0 -0
  122. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager.py +0 -0
  123. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager_errors.py +0 -0
  124. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager_flows.py +0 -0
  125. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager_mech.py +0 -0
  126. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager_rewards.py +0 -0
  127. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager_validation.py +0 -0
  128. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_staking.py +0 -0
  129. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_staking_integration.py +0 -0
  130. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_staking_validation.py +0 -0
  131. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tui/__init__.py +0 -0
  132. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tui/olas_view.py +0 -0
  133. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/__init__.py +0 -0
  134. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/check_profile.py +0 -0
  135. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/drain_accounts.py +0 -0
  136. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/list_contracts.py +0 -0
  137. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/release.py +0 -0
  138. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/reset_env.py +0 -0
  139. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/reset_tenderly.py +0 -0
  140. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/restore_backup.py +0 -0
  141. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/test_chainlist.py +0 -0
  142. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/wallet_check.py +0 -0
  143. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/__init__.py +0 -0
  144. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/app.py +0 -0
  145. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/modals/__init__.py +0 -0
  146. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/modals/base.py +0 -0
  147. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/rpc.py +0 -0
  148. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/screens/__init__.py +0 -0
  149. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/screens/wallets.py +0 -0
  150. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/tests/test_app.py +0 -0
  151. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/tests/test_rpc.py +0 -0
  152. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/tests/test_wallets_refactor.py +0 -0
  153. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/tests/test_widgets.py +0 -0
  154. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/widgets/__init__.py +0 -0
  155. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/widgets/base.py +0 -0
  156. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/workers.py +0 -0
  157. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/dependencies.py +0 -0
  158. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/models.py +0 -0
  159. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/accounts.py +0 -0
  160. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/__init__.py +0 -0
  161. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/admin.py +0 -0
  162. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/funding.py +0 -0
  163. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/general.py +0 -0
  164. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/services.py +0 -0
  165. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/staking.py +0 -0
  166. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/state.py +0 -0
  167. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/swap.py +0 -0
  168. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/transactions.py +0 -0
  169. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/server.py +0 -0
  170. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/static/app.js +0 -0
  171. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/static/index.html +0 -0
  172. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/static/style.css +0 -0
  173. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/tests/test_web_endpoints.py +0 -0
  174. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/tests/test_web_olas.py +0 -0
  175. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/tests/test_web_swap.py +0 -0
  176. {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/tests/test_web_swap_coverage.py +0 -0
  177. {iwa-0.0.61 → iwa-0.0.62}/src/iwa.egg-info/SOURCES.txt +0 -0
  178. {iwa-0.0.61 → iwa-0.0.62}/src/iwa.egg-info/dependency_links.txt +0 -0
  179. {iwa-0.0.61 → iwa-0.0.62}/src/iwa.egg-info/entry_points.txt +0 -0
  180. {iwa-0.0.61 → iwa-0.0.62}/src/iwa.egg-info/requires.txt +0 -0
  181. {iwa-0.0.61 → iwa-0.0.62}/src/iwa.egg-info/top_level.txt +0 -0
  182. {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_cow.py +0 -0
  183. {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_safe.py +0 -0
  184. {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_transaction_retry_logic.py +0 -0
  185. {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_tui.py +0 -0
  186. {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_wallets_screen.py +0 -0
  187. {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_web.py +0 -0
  188. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_account_service.py +0 -0
  189. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_balance_service.py +0 -0
  190. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_chain.py +0 -0
  191. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_chain_interface.py +0 -0
  192. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_chain_interface_coverage.py +0 -0
  193. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_cli.py +0 -0
  194. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_contract.py +0 -0
  195. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_db.py +0 -0
  196. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_drain_coverage.py +0 -0
  197. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_erc20.py +0 -0
  198. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_gnosis_plugin.py +0 -0
  199. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_keys.py +0 -0
  200. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_legacy_wallet.py +0 -0
  201. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_main.py +0 -0
  202. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_migration.py +0 -0
  203. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_mnemonic.py +0 -0
  204. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_modals.py +0 -0
  205. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_models.py +0 -0
  206. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_monitor.py +0 -0
  207. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_multisend.py +0 -0
  208. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_plugin_service.py +0 -0
  209. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rate_limiter.py +0 -0
  210. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rate_limiter_retry.py +0 -0
  211. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_reset_tenderly.py +0 -0
  212. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rpc_efficiency.py +0 -0
  213. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rpc_rate_limit.py +0 -0
  214. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rpc_rotation.py +0 -0
  215. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rpc_view.py +0 -0
  216. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_safe_coverage.py +0 -0
  217. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_safe_integration.py +0 -0
  218. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_safe_service.py +0 -0
  219. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_service_manager_integration.py +0 -0
  220. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_service_manager_structure.py +0 -0
  221. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_service_transaction.py +0 -0
  222. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_staking_router.py +0 -0
  223. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_staking_simple.py +0 -0
  224. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_tables.py +0 -0
  225. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transaction_service.py +0 -0
  226. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transfer_multisend.py +0 -0
  227. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transfer_native.py +0 -0
  228. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transfer_security.py +0 -0
  229. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transfer_structure.py +0 -0
  230. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transfer_swap_unit.py +0 -0
  231. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_ui_coverage.py +0 -0
  232. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_utils.py +0 -0
  233. {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_workers.py +0 -0
  234. {iwa-0.0.61 → iwa-0.0.62}/src/tools/create_and_stake_service.py +0 -0
  235. {iwa-0.0.61 → iwa-0.0.62}/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.61
3
+ Version: 0.0.62
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.61"
3
+ version = "0.0.62"
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"
@@ -72,7 +72,7 @@ where = ["src"]
72
72
 
73
73
  [tool.ruff]
74
74
  line-length = 100
75
- target-version = "0.0.61"
75
+ target-version = "0.0.62"
76
76
  fix = true
77
77
 
78
78
  [tool.ruff.lint]
@@ -285,11 +285,15 @@ class ChainInterface:
285
285
 
286
286
  # -- ChainList enrichment ----------------------------------------------
287
287
 
288
- MAX_RPCS = 10 # Cap total RPCs per chain
288
+ MAX_RPCS = 20 # Cap total RPCs per chain
289
289
 
290
290
  def _enrich_rpcs_from_chainlist(self) -> None:
291
291
  """Add validated public RPCs from ChainList to the rotation pool."""
292
292
  if len(self.chain.rpcs) >= self.MAX_RPCS:
293
+ logger.debug(
294
+ f"{self.chain.name}: skipping ChainList enrichment "
295
+ f"(already have {len(self.chain.rpcs)} RPCs)"
296
+ )
293
297
  return
294
298
 
295
299
  try:
@@ -354,35 +358,45 @@ class ChainInterface:
354
358
 
355
359
  if should_rotate:
356
360
  failed_index = self._current_rpc_index
361
+ failed_rpc = sanitize_rpc_url(self.chain.rpcs[failed_index]) if self.chain.rpcs else "?"
357
362
 
358
363
  # Apply per-RPC backoff so smart rotation skips this RPC.
359
364
  if result["is_quota_exceeded"]:
360
- error_type = "quota exceeded"
361
- self._mark_rpc_backoff(failed_index, self.QUOTA_EXCEEDED_BACKOFF)
365
+ error_type = "QUOTA"
366
+ backoff = self.QUOTA_EXCEEDED_BACKOFF
367
+ self._mark_rpc_backoff(failed_index, backoff)
362
368
  elif result["is_rate_limit"]:
363
- error_type = "rate limit"
364
- self._mark_rpc_backoff(failed_index, self.RATE_LIMIT_BACKOFF)
369
+ error_type = "RATE_LIMIT"
370
+ backoff = self.RATE_LIMIT_BACKOFF
371
+ self._mark_rpc_backoff(failed_index, backoff)
365
372
  # Brief global backoff so other threads don't immediately flood
366
373
  # the same (now backed-off) RPC before rotation takes effect.
367
374
  self._rate_limiter.trigger_backoff(seconds=2.0)
368
375
  else:
369
- error_type = "connection"
370
- self._mark_rpc_backoff(failed_index, self.CONNECTION_ERROR_BACKOFF)
376
+ error_type = "CONNECTION"
377
+ backoff = self.CONNECTION_ERROR_BACKOFF
378
+ self._mark_rpc_backoff(failed_index, backoff)
379
+
380
+ # Count healthy RPCs for visibility
381
+ healthy_count = sum(1 for i in range(len(self.chain.rpcs)) if self._is_rpc_healthy(i))
382
+ total_rpcs = len(self.chain.rpcs) if self.chain.rpcs else 0
371
383
 
372
384
  logger.warning(
373
- f"RPC {error_type} error on {self.chain.name} "
374
- f"(RPC #{failed_index}): {error}"
385
+ f"[{self.chain.name}] RPC #{failed_index} {error_type} → "
386
+ f"backoff {int(backoff)}s ({healthy_count}/{total_rpcs} healthy) | "
387
+ f"{failed_rpc}: {str(error)[:100]}"
375
388
  )
376
389
 
377
390
  if self.rotate_rpc():
378
391
  result["rotated"] = True
379
392
  result["should_retry"] = True
380
- logger.info(f"Rotated to RPC #{self._current_rpc_index} for {self.chain.name}")
393
+ new_rpc = sanitize_rpc_url(self.chain.rpcs[self._current_rpc_index])
394
+ logger.info(f"[{self.chain.name}] Rotated to RPC #{self._current_rpc_index}: {new_rpc}")
381
395
  else:
382
396
  # Rotation skipped (cooldown or single RPC) - still allow retry
383
397
  result["should_retry"] = True
384
- logger.info(
385
- f"RPC rotation skipped, retrying with current RPC #{self._current_rpc_index}"
398
+ logger.debug(
399
+ f"[{self.chain.name}] Rotation skipped (cooldown), retrying RPC #{self._current_rpc_index}"
386
400
  )
387
401
 
388
402
  elif result["is_server_error"]:
@@ -39,6 +39,10 @@ class SafeTransactionExecutor:
39
39
  MAX_GAS_MULTIPLIER = 10 # Hard cap: never exceed 10x original estimate
40
40
  DEFAULT_FALLBACK_GAS = 500_000 # Fallback when estimation fails
41
41
 
42
+ # Fee bumping for "max fee per gas less than block base fee" errors
43
+ FEE_BUMP_PERCENTAGE = 1.30 # 30% bump per retry on fee errors
44
+ MAX_FEE_BUMP_FACTOR = 3.0 # Cap: never bump more than 3x original
45
+
42
46
  def __init__(
43
47
  self,
44
48
  chain_interface: "ChainInterface",
@@ -76,6 +80,7 @@ class SafeTransactionExecutor:
76
80
  last_error = None
77
81
  current_gas = safe_tx.safe_tx_gas
78
82
  base_estimate = current_gas if current_gas > 0 else 0
83
+ fee_bump_factor = 1.0 # Multiplier for EIP-1559 fees, increases on fee errors
79
84
 
80
85
  for attempt in range(self.max_retries + 1):
81
86
  SAFE_TX_STATS["total_attempts"] += 1
@@ -89,6 +94,7 @@ class SafeTransactionExecutor:
89
94
  attempt,
90
95
  current_gas,
91
96
  base_estimate,
97
+ fee_bump_factor,
92
98
  )
93
99
 
94
100
  # Check receipt
@@ -106,7 +112,7 @@ class SafeTransactionExecutor:
106
112
  raise ValueError("Transaction reverted on-chain")
107
113
 
108
114
  except Exception as e:
109
- updated_tx, should_retry = self._handle_execution_failure(
115
+ updated_tx, should_retry, is_fee_error = self._handle_execution_failure(
110
116
  e, safe_address, safe_tx, attempt, operation_name
111
117
  )
112
118
  last_error = e
@@ -115,7 +121,12 @@ class SafeTransactionExecutor:
115
121
 
116
122
  # Update gas/nonce for next loop if needed
117
123
  safe_tx = updated_tx
118
- # If gas error, gas is recalculated in next _execute_attempt via fresh estimation
124
+
125
+ # Bump fee multiplier on fee-related errors (base fee > max fee)
126
+ if is_fee_error and fee_bump_factor < self.MAX_FEE_BUMP_FACTOR:
127
+ fee_bump_factor *= self.FEE_BUMP_PERCENTAGE
128
+ fee_bump_factor = min(fee_bump_factor, self.MAX_FEE_BUMP_FACTOR)
129
+ logger.info(f"[{operation_name}] Fee bump factor increased to {fee_bump_factor:.2f}x")
119
130
 
120
131
  delay = self.DEFAULT_RETRY_DELAY * (2**attempt)
121
132
  time.sleep(delay)
@@ -131,6 +142,7 @@ class SafeTransactionExecutor:
131
142
  attempt,
132
143
  current_gas,
133
144
  base_estimate,
145
+ fee_bump_factor: float = 1.0,
134
146
  ) -> str:
135
147
  """Prepare client, estimate gas, simulate, and execute."""
136
148
  # 1. (Re)Create Safe client
@@ -171,24 +183,11 @@ class SafeTransactionExecutor:
171
183
  signatures_backup = safe_tx.signatures
172
184
 
173
185
  try:
174
- # Always pass the first signer key as the executor (gas payer).
175
- # Note: This method does NOT re-sign the Safe hash if signatures are already present.
176
- # Use EIP-1559 'FAST' to ensure adequate priority fee (fixes Gnosis FeeTooLow)
177
- result = safe_tx.execute(signer_keys[0], eip1559_speed=TxSpeed.FAST)
178
-
179
- # Handle both tuple return (tx_hash, tx) and bytes return
180
- if isinstance(result, tuple):
181
- tx_hash_bytes = result[0]
182
- else:
183
- tx_hash_bytes = result
184
-
185
- # Handle both bytes and hex string returns
186
- if isinstance(tx_hash_bytes, bytes):
187
- return f"0x{tx_hash_bytes.hex()}"
188
- elif isinstance(tx_hash_bytes, str):
189
- return tx_hash_bytes if tx_hash_bytes.startswith("0x") else f"0x{tx_hash_bytes}"
190
- else:
191
- return str(tx_hash_bytes)
186
+ # Execute with appropriate gas pricing
187
+ result = self._execute_with_gas_pricing(
188
+ safe_tx, signer_keys[0], fee_bump_factor, operation_name
189
+ )
190
+ return self._extract_tx_hash(result)
192
191
 
193
192
  finally:
194
193
  # Restore signatures for next attempt if needed
@@ -196,6 +195,39 @@ class SafeTransactionExecutor:
196
195
  if safe_tx.signatures != signatures_backup:
197
196
  safe_tx.signatures = signatures_backup
198
197
 
198
+ def _execute_with_gas_pricing(
199
+ self, safe_tx: SafeTx, signer_key: str, fee_bump_factor: float, operation_name: str
200
+ ):
201
+ """Execute transaction with appropriate gas pricing strategy.
202
+
203
+ If fee_bump_factor > 1.0, calculates a bumped gas price to overcome
204
+ base fee volatility. Otherwise uses EIP-1559 FAST speed.
205
+ """
206
+ if fee_bump_factor > 1.0:
207
+ bumped_gas_price = self._calculate_bumped_gas_price(fee_bump_factor)
208
+ if bumped_gas_price:
209
+ logger.debug(
210
+ f"[{operation_name}] Using bumped gas price: {bumped_gas_price} wei "
211
+ f"(factor: {fee_bump_factor:.2f}x)"
212
+ )
213
+ return safe_tx.execute(signer_key, tx_gas_price=bumped_gas_price)
214
+ # Fallback to FAST if calculation fails
215
+ return safe_tx.execute(signer_key, eip1559_speed=TxSpeed.FAST)
216
+ # Default: use EIP-1559 'FAST' speed
217
+ return safe_tx.execute(signer_key, eip1559_speed=TxSpeed.FAST)
218
+
219
+ def _extract_tx_hash(self, result) -> str:
220
+ """Extract transaction hash from execute() result."""
221
+ # Handle both tuple return (tx_hash, tx) and bytes return
222
+ tx_hash_bytes = result[0] if isinstance(result, tuple) else result
223
+
224
+ # Handle both bytes and hex string returns
225
+ if isinstance(tx_hash_bytes, bytes):
226
+ return f"0x{tx_hash_bytes.hex()}"
227
+ if isinstance(tx_hash_bytes, str):
228
+ return tx_hash_bytes if tx_hash_bytes.startswith("0x") else f"0x{tx_hash_bytes}"
229
+ return str(tx_hash_bytes)
230
+
199
231
  def _check_receipt_status(self, receipt) -> bool:
200
232
  """Check if receipt has successful status."""
201
233
  status = getattr(receipt, "status", None)
@@ -210,14 +242,20 @@ class SafeTransactionExecutor:
210
242
  safe_tx: SafeTx,
211
243
  attempt: int,
212
244
  operation_name: str,
213
- ) -> Tuple[SafeTx, bool]:
214
- """Handle execution failure and determine next steps."""
245
+ ) -> Tuple[SafeTx, bool, bool]:
246
+ """Handle execution failure and determine next steps.
247
+
248
+ Returns:
249
+ Tuple of (updated_safe_tx, should_retry, is_fee_error)
250
+
251
+ """
215
252
  classification = self._classify_error(error)
253
+ is_fee_error = classification["is_fee_error"]
216
254
 
217
255
  if attempt >= self.max_retries:
218
256
  SAFE_TX_STATS["final_failures"] += 1
219
257
  logger.error(f"[{operation_name}] Failed after {attempt + 1} attempts: {error}")
220
- return safe_tx, False
258
+ return safe_tx, False, is_fee_error
221
259
 
222
260
  strategy = "retry"
223
261
  safe = self._recreate_safe_client(safe_address)
@@ -231,13 +269,16 @@ class SafeTransactionExecutor:
231
269
  SAFE_TX_STATS["rpc_rotations"] += 1
232
270
  result = self.chain_interface._handle_rpc_error(error)
233
271
  if not result["should_retry"]:
234
- return safe_tx, False
272
+ return safe_tx, False, is_fee_error
273
+ elif is_fee_error:
274
+ strategy = "fee bump"
275
+ SAFE_TX_STATS["gas_retries"] += 1
235
276
  elif classification["is_gas_error"]:
236
277
  strategy = "gas increase"
237
- # Gas increase handled in next attempt loop
278
+ SAFE_TX_STATS["gas_retries"] += 1
238
279
 
239
280
  self._log_retry(attempt + 1, error, strategy)
240
- return safe_tx, True
281
+ return safe_tx, True, is_fee_error
241
282
 
242
283
  def _estimate_safe_tx_gas(self, safe: Safe, safe_tx: SafeTx, base_estimate: int = 0) -> int:
243
284
  """Estimate gas for a Safe transaction with buffer and hard cap."""
@@ -320,14 +361,57 @@ class SafeTransactionExecutor:
320
361
  error
321
362
  ) or self.chain_interface._is_connection_error(error)
322
363
 
364
+ # Fee-specific errors: base fee jumped above our max fee
365
+ fee_error_signals = [
366
+ "max fee per gas less than block base fee",
367
+ "maxfeepergas",
368
+ "fee too low",
369
+ "underpriced",
370
+ ]
371
+ is_fee_error = any(signal in err_text for signal in fee_error_signals)
372
+
323
373
  return {
324
374
  "is_gas_error": any(x in err_text for x in ["gas", "out of gas", "intrinsic"]),
375
+ "is_fee_error": is_fee_error,
325
376
  "is_nonce_error": self._is_nonce_error(error),
326
377
  "is_rpc_error": is_rpc,
327
378
  "is_revert": "revert" in err_text or "execution reverted" in err_text,
328
379
  "is_signature_error": self._is_signature_error(error),
329
380
  }
330
381
 
382
+ def _calculate_bumped_gas_price(self, bump_factor: float) -> Optional[int]:
383
+ """Calculate a bumped gas price based on current base fee.
384
+
385
+ Uses legacy gas price (not EIP-1559) for compatibility with safe-eth-py's
386
+ tx_gas_price parameter. The bumped price ensures we're above the current
387
+ base fee even if it's volatile.
388
+
389
+ Args:
390
+ bump_factor: Multiplier to apply to the base fee (e.g., 1.3 = 30% bump)
391
+
392
+ Returns:
393
+ Gas price in wei, or None if calculation fails
394
+
395
+ """
396
+ try:
397
+ web3 = self.chain_interface.web3
398
+ latest_block = web3.eth.get_block("latest")
399
+ base_fee = latest_block.get("baseFeePerGas")
400
+
401
+ if base_fee is not None:
402
+ # EIP-1559 chain: calculate bumped max fee
403
+ # base_fee * bump_factor * 1.5 (extra buffer) + priority fee
404
+ priority_fee = max(int(web3.eth.max_priority_fee), 1)
405
+ bumped_fee = int(base_fee * bump_factor * 1.5) + priority_fee
406
+ return bumped_fee
407
+ else:
408
+ # Legacy chain: bump the gas price directly
409
+ gas_price = web3.eth.gas_price
410
+ return int(gas_price * bump_factor)
411
+ except Exception as e:
412
+ logger.debug(f"Failed to calculate bumped gas price: {e}")
413
+ return None
414
+
331
415
  def _decode_revert_reason(self, error: Exception) -> Optional[str]:
332
416
  """Attempt to decode the revert reason."""
333
417
  import re
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iwa
3
- Version: 0.0.61
3
+ Version: 0.0.62
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
@@ -343,12 +343,12 @@ class TestEnrichFromChainlist:
343
343
 
344
344
  chain = MagicMock(spec=SupportedChain)
345
345
  chain.name = "TestChain"
346
- chain.rpcs = [f"https://rpc{i}.example.com" for i in range(10)]
346
+ chain.rpcs = [f"https://rpc{i}.example.com" for i in range(20)]
347
347
  chain.rpc = "https://rpc0.example.com"
348
348
  chain.chain_id = 100
349
349
 
350
350
  with patch("iwa.core.chainlist.ChainlistRPC") as mock_cl_cls:
351
351
  ChainInterface(chain)
352
352
 
353
- # Already at MAX_RPCS=10, ChainlistRPC should not be called
353
+ # Already at MAX_RPCS=20, ChainlistRPC should not be called
354
354
  mock_cl_cls.assert_not_called()
@@ -567,3 +567,73 @@ def test_rpc_rotation_stops_when_should_not_retry(
567
567
  assert success is False
568
568
  # Only 1 attempt because should_retry=False
569
569
  assert mock_safe_tx.execute.call_count == 1
570
+
571
+
572
+ # =============================================================================
573
+ # Test: Fee bumping on base fee errors
574
+ # =============================================================================
575
+
576
+
577
+ def test_fee_error_triggers_bump(executor, mock_chain_interface, mock_safe_tx, mock_safe):
578
+ """Test that fee errors trigger gas price bump on retry."""
579
+ with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
580
+ # First attempt fails with fee error, second succeeds
581
+ mock_safe_tx.execute.side_effect = [
582
+ ValueError("max fee per gas less than block base fee: maxFeePerGas: 596, baseFee: 681"),
583
+ b"tx_hash",
584
+ ]
585
+ mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
586
+ status=1
587
+ )
588
+ # Mock fee calculation
589
+ mock_chain_interface.web3.eth.get_block.return_value = {"baseFeePerGas": 700}
590
+ mock_chain_interface.web3.eth.max_priority_fee = 1
591
+
592
+ with patch("time.sleep"):
593
+ success, tx_hash, receipt = executor.execute_with_retry(
594
+ "0xSafe", mock_safe_tx, ["key1"]
595
+ )
596
+
597
+ assert success is True
598
+ assert mock_safe_tx.execute.call_count == 2
599
+ # Second call should have tx_gas_price (bumped), not eip1559_speed
600
+ second_call_kwargs = mock_safe_tx.execute.call_args_list[1][1]
601
+ assert "tx_gas_price" in second_call_kwargs
602
+
603
+
604
+ def test_fee_error_classification(executor):
605
+ """Test classification of fee-related errors."""
606
+ fee_errors = [
607
+ "max fee per gas less than block base fee",
608
+ "transaction underpriced",
609
+ "maxFeePerGas too low",
610
+ "fee too low for mempool",
611
+ ]
612
+ for error_msg in fee_errors:
613
+ error = ValueError(error_msg)
614
+ result = executor._classify_error(error)
615
+ assert result["is_fee_error"] is True, f"Should detect fee error: {error_msg}"
616
+
617
+
618
+ def test_calculate_bumped_gas_price_eip1559(executor, mock_chain_interface):
619
+ """Test bumped gas price calculation for EIP-1559 chains."""
620
+ mock_chain_interface.web3.eth.get_block.return_value = {"baseFeePerGas": 1000}
621
+ mock_chain_interface.web3.eth.max_priority_fee = 10
622
+
623
+ # With 1.3x bump factor: base_fee * 1.3 * 1.5 + priority = 1000 * 1.3 * 1.5 + 10 = 1960
624
+ result = executor._calculate_bumped_gas_price(1.3)
625
+
626
+ assert result is not None
627
+ assert result == int(1000 * 1.3 * 1.5) + 10
628
+
629
+
630
+ def test_calculate_bumped_gas_price_legacy(executor, mock_chain_interface):
631
+ """Test bumped gas price calculation for legacy chains."""
632
+ mock_chain_interface.web3.eth.get_block.return_value = {} # No baseFeePerGas
633
+ mock_chain_interface.web3.eth.gas_price = 2000
634
+
635
+ # Legacy: gas_price * bump_factor = 2000 * 1.3 = 2600
636
+ result = executor._calculate_bumped_gas_price(1.3)
637
+
638
+ assert result is not None
639
+ assert result == int(2000 * 1.3)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes