iwa 0.0.2__py3-none-any.whl → 0.0.10__py3-none-any.whl

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 (50) hide show
  1. iwa/core/chain/interface.py +30 -23
  2. iwa/core/chain/models.py +9 -15
  3. iwa/core/contracts/contract.py +8 -2
  4. iwa/core/pricing.py +10 -8
  5. iwa/core/services/safe.py +13 -8
  6. iwa/core/services/transaction.py +15 -4
  7. iwa/core/utils.py +22 -0
  8. iwa/plugins/gnosis/safe.py +4 -3
  9. iwa/plugins/gnosis/tests/test_safe.py +9 -7
  10. iwa/plugins/olas/contracts/service.py +4 -4
  11. iwa/plugins/olas/contracts/staking.py +2 -3
  12. iwa/plugins/olas/plugin.py +14 -7
  13. iwa/plugins/olas/service_manager/lifecycle.py +109 -48
  14. iwa/plugins/olas/service_manager/mech.py +1 -1
  15. iwa/plugins/olas/service_manager/staking.py +92 -34
  16. iwa/plugins/olas/tests/test_plugin.py +6 -1
  17. iwa/plugins/olas/tests/test_plugin_full.py +12 -7
  18. iwa/plugins/olas/tests/test_service_manager_validation.py +16 -15
  19. iwa/tools/list_contracts.py +2 -2
  20. iwa/web/dependencies.py +1 -3
  21. iwa/web/routers/accounts.py +1 -2
  22. iwa/web/routers/olas/admin.py +1 -3
  23. iwa/web/routers/olas/funding.py +1 -3
  24. iwa/web/routers/olas/general.py +1 -3
  25. iwa/web/routers/olas/services.py +1 -2
  26. iwa/web/routers/olas/staking.py +19 -22
  27. iwa/web/routers/swap.py +1 -2
  28. iwa/web/routers/transactions.py +0 -2
  29. iwa/web/server.py +8 -6
  30. iwa/web/static/app.js +22 -0
  31. iwa/web/tests/test_web_endpoints.py +1 -1
  32. iwa/web/tests/test_web_olas.py +1 -1
  33. {iwa-0.0.2.dist-info → iwa-0.0.10.dist-info}/METADATA +1 -1
  34. {iwa-0.0.2.dist-info → iwa-0.0.10.dist-info}/RECORD +50 -49
  35. tests/test_chain_interface_coverage.py +3 -2
  36. tests/test_contract.py +165 -0
  37. tests/test_keys.py +2 -1
  38. tests/test_legacy_wallet.py +11 -0
  39. tests/test_pricing.py +32 -15
  40. tests/test_safe_coverage.py +3 -3
  41. tests/test_safe_service.py +3 -6
  42. tests/test_service_transaction.py +8 -3
  43. tests/test_staking_router.py +6 -3
  44. tests/test_transaction_service.py +4 -0
  45. tools/create_and_stake_service.py +103 -0
  46. tools/verify_drain.py +1 -4
  47. {iwa-0.0.2.dist-info → iwa-0.0.10.dist-info}/WHEEL +0 -0
  48. {iwa-0.0.2.dist-info → iwa-0.0.10.dist-info}/entry_points.txt +0 -0
  49. {iwa-0.0.2.dist-info → iwa-0.0.10.dist-info}/licenses/LICENSE +0 -0
  50. {iwa-0.0.2.dist-info → iwa-0.0.10.dist-info}/top_level.txt +0 -0
@@ -10,22 +10,22 @@ iwa/core/mnemonic.py,sha256=LiG1VmpydQoHQ0pHUJ1OIlrWJry47VSMnOqPM_Yk-O8,12930
10
10
  iwa/core/models.py,sha256=kBQ0cBe6uFmL2QfW7mjKiMFeZxhT-FRN-RyK3Ko0vE8,12849
11
11
  iwa/core/monitor.py,sha256=OmhKVMkfhvtxig3wDUL6iGwBIClTx0YUqMncCao4SqI,7953
12
12
  iwa/core/plugins.py,sha256=FLvOG4S397fKi0aTH1fWBEtexn4yvGv_QzGWqFrhSKE,1102
13
- iwa/core/pricing.py,sha256=5D2WAaDRuY1iOmj9fwgh_etnjjU6wdJmIgwoy8DbA2E,3614
13
+ iwa/core/pricing.py,sha256=B8dAUszv9t5EqvXQ7gh9CNBqlMLR598-c6Pv3NqtpO4,3561
14
14
  iwa/core/secrets.py,sha256=U7DZKrwKuSOFV00Ij3ISrrO1cWn_t1GBW_0PyAqjcD4,2588
15
15
  iwa/core/tables.py,sha256=y7Cg67PAGHYVMVyAjbo_CQ9t2iz7UXE-OTuUHRyFRTo,2021
16
16
  iwa/core/test.py,sha256=gey0dql5eajo1itOhgkSrgfyGWue2eSfpr0xzX3vc38,643
17
17
  iwa/core/types.py,sha256=EfDfIwLajTNK-BP9K17QLOIsGCs8legplKI_bUD_NjM,1992
18
18
  iwa/core/ui.py,sha256=DglmrI7XhUmOpLn9Nog9Cej4r-VT0JGFkuSNBx-XorQ,3131
19
- iwa/core/utils.py,sha256=Jc5yvyltYhnnPC9GdvNh_fW6bRuMen0XiG1nQcIuvhQ,2160
19
+ iwa/core/utils.py,sha256=5fc8Ffd3F47iFb8QbnZmKh_d2Fsh1ZytVG2HFnhySEQ,2950
20
20
  iwa/core/wallet.py,sha256=ZxztWOK8MzIih7nGT52BUvE6Z5a9AA056IV_gqMPQpA,12972
21
21
  iwa/core/chain/__init__.py,sha256=XJMmn0ed-_aVkY2iEMKpuTxPgIKBd41dexSVmEZTa-o,1604
22
22
  iwa/core/chain/errors.py,sha256=9SEbhxZ-qASPkzt-DoI51qq0GRJVqRgqgL720gO7a64,1275
23
- iwa/core/chain/interface.py,sha256=3tyMvnOZCTTvM0Y3YgxuN6zlXyJ1EIZUoyVosGtsUI8,20228
23
+ iwa/core/chain/interface.py,sha256=b6Nac4Wg4zygu9B81MTMswt96Ls_--GbPJuEOKu5loY,20497
24
24
  iwa/core/chain/manager.py,sha256=cFEzh6pK5OyVhjhpeMAqhc9RnRDQR1DjIGiGKp-FXBI,1159
25
- iwa/core/chain/models.py,sha256=h3-vygtsAHaY1-JSqTujr0JYs7OpzBfYTRsh-vOoEV4,4962
25
+ iwa/core/chain/models.py,sha256=0OgBo08FZEQisOdd00YUMXSAV7BC0CcWpqJ2y-gs0cI,4863
26
26
  iwa/core/chain/rate_limiter.py,sha256=gU7TmWdH9D_wbXKT1X7mIgoIUCWVuebgvRhxiyLGAmI,6613
27
27
  iwa/core/contracts/__init__.py,sha256=P5GFY_pnuI02teqVY2U0t98bn1_SSPAbcAzRMpCdTi4,34
28
- iwa/core/contracts/contract.py,sha256=ERWViqZNqhsmf-tKVxn5XfQTJ8S_1MXVk0Tjq8davd0,11228
28
+ iwa/core/contracts/contract.py,sha256=qHU1bvo1uhL2smY-dOrLSwVo6fb0ouWcpMsralGwJio,11440
29
29
  iwa/core/contracts/erc20.py,sha256=VqriOdUXej0ilTgpukpm1FUF_9sSrVMAPuEpIvyZ2SQ,2646
30
30
  iwa/core/contracts/multisend.py,sha256=tSdBCWe7LSdBoKZ7z2QebmRFK4M2ln7H3kmJBeEb4Ho,2431
31
31
  iwa/core/contracts/abis/erc20.json,sha256=vrdExMWcIogg_nO59j1Pmipmpa2Ulj3oCCdcdrrFVCE,16995
@@ -35,8 +35,8 @@ iwa/core/services/__init__.py,sha256=ab5pYzmu3LrZLTO5N-plx6Rp4R0hBEnbbzsgz84zWGM
35
35
  iwa/core/services/account.py,sha256=01MoEvl6FJlMnMB4fGwsPtnGa4kgA-d5hJeKu_ACg7Y,1982
36
36
  iwa/core/services/balance.py,sha256=mPE12CuOFfCaJXaQXWOcQM1O03ZF3ghpy_-oOjNk_GE,4104
37
37
  iwa/core/services/plugin.py,sha256=GNNlbtELyHl7MNVChrypF76GYphxXduxDog4kx1MLi8,3277
38
- iwa/core/services/safe.py,sha256=ULrzawkqcKjq-ffA0evndfp4OXi_VPVR9Yozvow8rFk,14587
39
- iwa/core/services/transaction.py,sha256=RsZmkTLAZbmH9oimuJ9qXes23e5Z9mf1WnMxyNiYMCM,6928
38
+ iwa/core/services/safe.py,sha256=ytNJMndXrzTMHwhDZKYLIh4Q0UTWDBgQgTpof-UqIkA,14827
39
+ iwa/core/services/transaction.py,sha256=81jiGF2cxhyy0Oh1jw6uyl0q10oZxOCLnsE3dtOgHJ0,7375
40
40
  iwa/core/services/transfer/__init__.py,sha256=ZJfshFxJRsp8rkOqfVvd1cqEzIJ9tqBJh8pc0l90GLk,5576
41
41
  iwa/core/services/transfer/base.py,sha256=sohz-Ss2i-pGYGl4x9bD93cnYKcSvsXaXyvyRawvgQs,9043
42
42
  iwa/core/services/transfer/erc20.py,sha256=0GvlfETgdJg15azs1apH1gI6Vm3gAv22o1saNbIfiAY,8685
@@ -48,25 +48,25 @@ iwa/plugins/__init__.py,sha256=zy-DjOZn8GSgIETN2X_GAb9O6yk71t6ZRzeUgoZ52KA,23
48
48
  iwa/plugins/gnosis/__init__.py,sha256=dpx0mE84eV-g5iZaH5nKivZJnoKWyRFX5rhdjowBwuU,114
49
49
  iwa/plugins/gnosis/cow_utils.py,sha256=iSvbfgTr2bCqRsUznKCWqmoTnyuX-WZX4oh0E-l3XBU,2263
50
50
  iwa/plugins/gnosis/plugin.py,sha256=AgkgOGYfnrcjWrPUiAvySMj6ITnss0SFXiEi6Z6fnMs,1885
51
- iwa/plugins/gnosis/safe.py,sha256=MUjb3VyoRVU5NlcLLtVfzPd-LmANfvHtsgqbG8YOa44,5505
51
+ iwa/plugins/gnosis/safe.py,sha256=aDcBz50Bliur90sVqxgjGoQJyCqsfZlm0PDsmWIDBak,5517
52
52
  iwa/plugins/gnosis/cow/__init__.py,sha256=lZN5QpIYWL67rE8r7z7zS9dlr8OqFrYeD9T4-RwUghU,224
53
53
  iwa/plugins/gnosis/cow/quotes.py,sha256=u2xFKgL7QTKqCkSPMv1RHaXvZ6WzID4haaZDMVS42Bs,5177
54
54
  iwa/plugins/gnosis/cow/swap.py,sha256=XZdvJbTbh54hxer7cKkum7lNQ-03gddMK95K3MenaFE,15209
55
55
  iwa/plugins/gnosis/cow/types.py,sha256=-9VRiFhAkmN1iIJ95Pg7zLFSeXtkkW00sl13usxi3o8,470
56
56
  iwa/plugins/gnosis/tests/test_cow.py,sha256=iVy5ockMIcPZWsX4WGXU91DhBsYEZ5NOxtFzAQ2sK3o,8440
57
- iwa/plugins/gnosis/tests/test_safe.py,sha256=BY6MJZqONrvIIougRpOQQsTNDOAjUsKXToTcwdMby44,3084
57
+ iwa/plugins/gnosis/tests/test_safe.py,sha256=pw1zrYvAiVtmPIU5k7BtOQpDNAQTSTrLIaeljCjSahc,3216
58
58
  iwa/plugins/olas/__init__.py,sha256=_NhBczzM61fhGYwGhnWfEeL8Jywyy_730GASe2BxzeQ,106
59
59
  iwa/plugins/olas/constants.py,sha256=mFUMDB46WbM9Y8sw7c-i9TRyJVrDNZXdCXa64M5tI6A,6608
60
60
  iwa/plugins/olas/importer.py,sha256=f8KlZ9dGcNbpg8uoTYbO9sDvbluZoslhpWFLqPjPnLA,26717
61
61
  iwa/plugins/olas/mech_reference.py,sha256=CaSCpQnQL4F7wOG6Ox6Zdoy-uNEQ78YBwVLILQZKL8Q,5782
62
62
  iwa/plugins/olas/models.py,sha256=xC5hYakX53pBT6zZteM9cyiC7t6XRLLpobjQmDYueOo,3520
63
- iwa/plugins/olas/plugin.py,sha256=wncoujfR1YXhJeCYwLxaX5J50OzoA3KUG8q02DRWDtA,9231
63
+ iwa/plugins/olas/plugin.py,sha256=S_vnvZ02VdVWD7N5kp7u5JIRQ2JLtfwGDZ7OHkAN0M8,9390
64
64
  iwa/plugins/olas/contracts/activity_checker.py,sha256=UDbCgFOyuEhHf1qoUwCUdLc8oK8ksuAYE-ZgKrMBnwg,3415
65
65
  iwa/plugins/olas/contracts/base.py,sha256=y73aQbDq6l4zUpz_eQAg4MsLkTAEqjjupXlcvxjfgCI,240
66
66
  iwa/plugins/olas/contracts/mech.py,sha256=dXYtyORc-oiu9ga5PtTquOFkoakb6BLGKvlUsteygIg,2767
67
67
  iwa/plugins/olas/contracts/mech_marketplace.py,sha256=hMADl5MQGvT2wLRKu4vHGe4RrAZVq8Y2M_EvXWWz528,1554
68
- iwa/plugins/olas/contracts/service.py,sha256=OHXi4TBaVlRRZJA15XKnTMN8huGDWRZWU1TOZf589_E,6731
69
- iwa/plugins/olas/contracts/staking.py,sha256=pxT62Cal1kkDfZdhbPk2YxyM2Xbf8-oFa40lL0Y8HBk,14520
68
+ iwa/plugins/olas/contracts/service.py,sha256=jGCA51qhpVhguRfVPRnfL08WNQBbruvxiCaiispRm3U,6739
69
+ iwa/plugins/olas/contracts/staking.py,sha256=vt2UZ6G0edg1EbPrzFK5AIse531bQTu4PLydQ6rgrAA,14494
70
70
  iwa/plugins/olas/contracts/abis/activity_checker.json,sha256=HT0IMbyTLMO71ITBKwoS950rHe772suPP4b8eDAodJ0,2230
71
71
  iwa/plugins/olas/contracts/abis/mech.json,sha256=bMMCXInjE_2PTPnc_sIyS_H8pod5Sm_e-xTbKgZppKc,16369
72
72
  iwa/plugins/olas/contracts/abis/mech_marketplace.json,sha256=KPnF-H_UATb3wdb_7o6ky_hSp5xwgvckD-QqylsWJLg,32468
@@ -80,9 +80,9 @@ iwa/plugins/olas/scripts/test_simple_lifecycle.py,sha256=8T50tOZx3afeECSfCNAb0rA
80
80
  iwa/plugins/olas/service_manager/__init__.py,sha256=GXiThMEY3nPgHUl1i-DLrF4h96z9jPxxI8Jepo2E1PM,1926
81
81
  iwa/plugins/olas/service_manager/base.py,sha256=V4o71ZYUSc_GzM-RWocaAGsrqI-fa5V7CcyloVpcjwE,4666
82
82
  iwa/plugins/olas/service_manager/drain.py,sha256=IS7YYKuQdkULcNdxfHVzjcq95pXKdpajolzLL78u4jc,12430
83
- iwa/plugins/olas/service_manager/lifecycle.py,sha256=-WG1ZQmC5F3vrqkMPX9iS_gcCmO_bSqAUHA3C6P8bpo,34161
84
- iwa/plugins/olas/service_manager/mech.py,sha256=jIdDzJlz-iNNrC-SErYmcbmKiEk6-zaYIGvzeQx12C4,12217
85
- iwa/plugins/olas/service_manager/staking.py,sha256=nZHB_dQ-jdMs2RtSrsmeIeaww9dQeVDqwlItiP7cp2Y,20772
83
+ iwa/plugins/olas/service_manager/lifecycle.py,sha256=0jGLoqNLRuIqzpv2VVtAqVtnuKSwkzhWw8-m8qknjQU,36834
84
+ iwa/plugins/olas/service_manager/mech.py,sha256=72-tEap-aYd0gebcH6y_De1SNeL6OrXn7sWuv_Ok_-Y,12224
85
+ iwa/plugins/olas/service_manager/staking.py,sha256=zwI2IREh502DDjhgKDyHr7puXRi46sQ7xp7KgbcZf3w,23931
86
86
  iwa/plugins/olas/tests/conftest.py,sha256=4vM7EI00SrTGyeP0hNzsGSQHEj2-iznVgzlNh2_OGfo,739
87
87
  iwa/plugins/olas/tests/test_importer.py,sha256=i9LKov7kNRECB3hmRnhKBwcfx3uxtjWe4BB77bOOpeo,4282
88
88
  iwa/plugins/olas/tests/test_importer_error_handling.py,sha256=X37TrvJ6-3-NuJ2megm0Cnx3KJdA1wke563pRf_EPJk,12084
@@ -93,15 +93,15 @@ iwa/plugins/olas/tests/test_olas_models.py,sha256=5scX-wvRLGH3G44S2okq_tyQ9Rk7Pd
93
93
  iwa/plugins/olas/tests/test_olas_view.py,sha256=kh3crsriyoRiZC6l8vzGllocvQnYmqziv-csbmXih0E,10489
94
94
  iwa/plugins/olas/tests/test_olas_view_actions.py,sha256=jAxr9bjFNAaxGf1btIrxdMaHgJ0PWX9aDwVU-oPGMpk,5109
95
95
  iwa/plugins/olas/tests/test_olas_view_modals.py,sha256=8j0PNFjKqFC5V1kBdVFWNLMvqGt49H6fLSYGxn02c8o,5562
96
- iwa/plugins/olas/tests/test_plugin.py,sha256=UIEgW_VMivqJllOAd3mmTltM0DF_ZBK79ecpIiBricI,2429
97
- iwa/plugins/olas/tests/test_plugin_full.py,sha256=RdDY_fYGkpfP3s742yBBh-gPr5uc1dfhZsrpGCrCeu8,8224
96
+ iwa/plugins/olas/tests/test_plugin.py,sha256=RVgU-Cq6t_3mOh90xFAGwlJOV7ZIgp0VNaK5ZAxisAQ,2565
97
+ iwa/plugins/olas/tests/test_plugin_full.py,sha256=xevLYmdU4zd5FyEEEn9gvzGimPztmt6vymybeZHXnq8,8507
98
98
  iwa/plugins/olas/tests/test_service_lifecycle.py,sha256=dhIDzgcXh1VV-S6VGXtNqtdxxIdXY1F1hRcPL6RBxno,5649
99
99
  iwa/plugins/olas/tests/test_service_manager.py,sha256=dsnj1DfSYptIZkiPaLHGM9utbX9XOAy0axuzu-3kofg,41818
100
100
  iwa/plugins/olas/tests/test_service_manager_errors.py,sha256=BtN-89gwaFpy3RJIDt98U0TT0lksg9NHvoSpSBHSXag,8229
101
101
  iwa/plugins/olas/tests/test_service_manager_flows.py,sha256=HsAD1axnfow1iTiRBqR7DT6sa3U-q8ApOlYN4Cd87Ao,20142
102
102
  iwa/plugins/olas/tests/test_service_manager_mech.py,sha256=qG6qu5IPRNypXUsblU2OEkuiuwDJ0TH8RXZbibmTFcQ,4937
103
103
  iwa/plugins/olas/tests/test_service_manager_rewards.py,sha256=hIVckGl5rEr-KHGNUxxVopal0Tpw4EZharBt0MbaCVA,11798
104
- iwa/plugins/olas/tests/test_service_manager_validation.py,sha256=dagFvq8eQk3HKYpajp_2uJ4AUi_74kIAMfTiN4qAZ58,5263
104
+ iwa/plugins/olas/tests/test_service_manager_validation.py,sha256=ajlfH5uc4mAHf8A7GLE5cW7X8utM2vUilM0JdGDdlVg,5382
105
105
  iwa/plugins/olas/tests/test_service_staking.py,sha256=XkC8H_qVumOQ1i4_vidEdXqkK5stInd2tgT50l0SRMk,12201
106
106
  iwa/plugins/olas/tests/test_staking_integration.py,sha256=zQdhhSLz5h2OvQ6GOJ-Yl_Y17itPML4Pr9VPkjWoz9c,9420
107
107
  iwa/plugins/olas/tests/test_staking_validation.py,sha256=J7DgDdIiVTkKv_7obtSHQ2lgfUGPmUwuPjTUkiT4cbs,4198
@@ -109,7 +109,7 @@ iwa/plugins/olas/tui/__init__.py,sha256=5ZRsbC7J3z1xfkZRiwr4bLEklf78rNVjdswe2p7S
109
109
  iwa/plugins/olas/tui/olas_view.py,sha256=qcPxhurDPJjHWln6R64ZVAJ2h82IXzw48unhRvQVZqQ,36448
110
110
  iwa/tools/__init__.py,sha256=jQyuwDQGRigSe7S9JMb4yK3CXPgZFJNffzt6N2v9PU0,21
111
111
  iwa/tools/check_profile.py,sha256=0LAv9wx4wMM610mX88-6tIoDi2I5LDzh0W9nkprt42s,2177
112
- iwa/tools/list_contracts.py,sha256=m5-m_zDuKENlCf6LiDFRh0gKcwVhrP-XNzsyy_upwV0,4864
112
+ iwa/tools/list_contracts.py,sha256=2w-LYB2RVS-eGil2kLiBIEm3tYYhYzT4lAuGO6VtLJU,4861
113
113
  iwa/tools/release.py,sha256=-Z9GG6Y-K6KG32K0VUf_MruiUdJxG6W7ToOMzhyCH7Y,3963
114
114
  iwa/tools/reset_env.py,sha256=FKN0wuh9Xq00c94B2kEFehHPKcWldxYqgU45yJwg5Cg,3140
115
115
  iwa/tools/reset_tenderly.py,sha256=w1-KujdqRpimrgiREvoRwVXsGsGoYisCLmYTcsJ6Np8,11295
@@ -129,27 +129,27 @@ iwa/tui/tests/test_wallets_refactor.py,sha256=71G3HLbhTtgDy3ffVbYv0MFYRgdYd-NWGB
129
129
  iwa/tui/tests/test_widgets.py,sha256=C9UgIGeWRaQ459JygFEQx-7hOi9mWrSUDDIMZH1ge50,3994
130
130
  iwa/tui/widgets/__init__.py,sha256=UzD6nJbwv9hOtkWl9I7faXm1a-rcu4xFRxrf4KBwwY4,161
131
131
  iwa/tui/widgets/base.py,sha256=G844GU61qSS6AgY5NxmOVHTghbgHlyTfo8hhE2VUrqQ,3379
132
- iwa/web/dependencies.py,sha256=xy99q7mJr-kCyh2hZAI0beaOtXVfppJbNmLhhbdam-g,2130
132
+ iwa/web/dependencies.py,sha256=0_dAJlRh6gKrUDRPKUe92eshFsg572yx_H0lQgSqGDA,2103
133
133
  iwa/web/models.py,sha256=MSD9WPy_Nz_amWgoo2KSDTn4ZLv_AV0o0amuNtSf-68,3035
134
- iwa/web/server.py,sha256=_7kDO-1J9znIGcOzdUuCBEePoAfjdubAmp6mxX7k2g8,5100
135
- iwa/web/routers/accounts.py,sha256=j6xFkKGeLkjpUG48vW8ZFsWzLxl-Vi5et7rcTsOJkiI,3942
134
+ iwa/web/server.py,sha256=4ZLVFEKoGs_NoCcXMeyYzDNdxUXazjwHQaX7CR1pwHE,5239
135
+ iwa/web/routers/accounts.py,sha256=HP7DdUbbnRb1dFCs7zqyhx61_IBJwDMKtmXpvAPZodg,3916
136
136
  iwa/web/routers/state.py,sha256=aEfeGFAOyaS6kWhXuWLrdvEbnQAeonKIWxJrQkqJ1GY,2198
137
- iwa/web/routers/swap.py,sha256=mU9StUZpJPvD8De412nmwlCYHRnGR4ISVl1YKF3gIb8,22656
138
- iwa/web/routers/transactions.py,sha256=ZDKaqCTt1ZylzxyfFQHxFPwzD-j9H0jUByYFSWvggBA,5654
137
+ iwa/web/routers/swap.py,sha256=8xycAytquR29ELxW3vx428W8s9bI_w_x2kpRhhJ0KXY,22630
138
+ iwa/web/routers/transactions.py,sha256=bRjfD7zcuX3orVIHzOMVsIkEtcE_pM_KCom4cdAKV6Q,5602
139
139
  iwa/web/routers/olas/__init__.py,sha256=Jo6Dm1e8fHafCD800fbxsxeFz3tvuEEKXEf5-9tj5Qg,947
140
- iwa/web/routers/olas/admin.py,sha256=ZFUTsA7k2xI16kSQ6OybN3jtJQQxh71Y5-Dc2s31rRI,5812
141
- iwa/web/routers/olas/funding.py,sha256=Jko_uZynE9cahf7ObjHfR54_9jNeU2m2hOJ8PUxegvI,4859
142
- iwa/web/routers/olas/general.py,sha256=gsL-bkOWTHLxw65Bt3T61123QUZpjxwbNkrx5Qo7s5E,830
143
- iwa/web/routers/olas/services.py,sha256=5W4Cvj5HQjxmEokPfVpaV1v_YKwOcm6SQfyXsgwpBZo,14766
144
- iwa/web/routers/olas/staking.py,sha256=9FH-yNUuG2KJxaI6HYn3d6fY5QvXd8UPCx01mDFYSgQ,12090
145
- iwa/web/static/app.js,sha256=CI9daKLWei3DpnMgUz693HqlCRrYN3ZEVi-wFI01t5U,112769
140
+ iwa/web/routers/olas/admin.py,sha256=PMRdNelqYgQ1xbqh3floFV5xrVtBRQiwZPd8J9_ffxg,5785
141
+ iwa/web/routers/olas/funding.py,sha256=f8fADNtbZEBFl-vuVKfas6os38Vot6K5tJBTenZmCD0,4832
142
+ iwa/web/routers/olas/general.py,sha256=dPsBQppTGoQY1RztliUhseOHOZGeeCR10lhThD9kyXo,803
143
+ iwa/web/routers/olas/services.py,sha256=fq_NBRlXlc-cHpAJYwZ7KsDqgcnB7vSCFqrHZnsLiYQ,14740
144
+ iwa/web/routers/olas/staking.py,sha256=3f5flyAb9uDdw6WKH3f68ERA6x_6geJGqOGeTMz-Rbs,12128
145
+ iwa/web/static/app.js,sha256=BCpAp1BcFVcfEfqXa-9gOtjb-8YlV5NIPWgSeCchD1E,113472
146
146
  iwa/web/static/index.html,sha256=q7s7plnMbN1Nkzr5bRxZgvgOFerUChEGIZW7SpAVtPc,28514
147
147
  iwa/web/static/style.css,sha256=aTtE42mmfYV6y7xfo9cUgUhT8x-KyNC1zmPjSdskxIk,24315
148
- iwa/web/tests/test_web_endpoints.py,sha256=vZUGmrudkQHA8gBmSyO8i2SgZpsr1He-YRm2JFAs9lg,23979
149
- iwa/web/tests/test_web_olas.py,sha256=oN__4nUT_H_j-_ALEqSp-si5xMbY0Bcxhvhuhxsg4Q8,16292
148
+ iwa/web/tests/test_web_endpoints.py,sha256=C264MH-CTyDW4GLUrTXBgLJKUk4-89pFAScBddd4Fvk,23995
149
+ iwa/web/tests/test_web_olas.py,sha256=0CVSsrncOeJ3x0ECV7mVLQV_CXZRrOqGiVjgLIi6hZ8,16308
150
150
  iwa/web/tests/test_web_swap.py,sha256=7A4gBJFL01kIXPtW1E1J17SCsVc_0DmUn-R8kKrnnVA,2974
151
151
  iwa/web/tests/test_web_swap_coverage.py,sha256=zGNrzlhZ_vWDCvWmLcoUwFgqxnrp_ACbo49AtWBS_Kw,5584
152
- iwa-0.0.2.dist-info/licenses/LICENSE,sha256=eIubm_IlBHPYRQlLNZKbBNKhJUUP3JH0A2miZUhAVfI,1078
152
+ iwa-0.0.10.dist-info/licenses/LICENSE,sha256=eIubm_IlBHPYRQlLNZKbBNKhJUUP3JH0A2miZUhAVfI,1078
153
153
  tests/legacy_cow.py,sha256=oOkZvIxL70ReEoD9oHQbOD5GpjIr6AGNHcOCgfPlerU,8389
154
154
  tests/legacy_safe.py,sha256=AssM2g13E74dNGODu_H0Q0y412lgqsrYnEzI97nm_Ts,2972
155
155
  tests/legacy_transaction_retry_logic.py,sha256=D9RqZ7DBu61Xr2djBAodU2p9UE939LL-DnQXswX5iQk,1497
@@ -160,15 +160,15 @@ tests/test_account_service.py,sha256=g_AIVT2jhlvUtbFTaCd-d15x4CmXJQaV66tlAgnaXwY
160
160
  tests/test_balance_service.py,sha256=86iEkPd2M1-UFy3qOxV1EguQOEYbboy2-2mAyS3ctGs,6549
161
161
  tests/test_chain.py,sha256=uXbNl9wD4e2qV1xs_MO74Soj3iaH-5xoxge4rD3FypU,16986
162
162
  tests/test_chain_interface.py,sha256=Wu0q0sREtmYBp7YvWrBIrrSTtqeQj18oJp2VmMUEMec,8312
163
- tests/test_chain_interface_coverage.py,sha256=cmZGxpBjf8pT9PwtECMqBbrUMaAJu6v3D8C18nfPyQ0,3248
163
+ tests/test_chain_interface_coverage.py,sha256=fvrVvw8-DMwdsSFKQHUhpbfutrVRxnnTc-tjB7Bb-jo,3327
164
164
  tests/test_cli.py,sha256=WW6EDeHLws5-BqFNOy11pH_D5lttuyspD5hrDCFpR0Q,3968
165
- tests/test_contract.py,sha256=XigxdTziO5SJVbg8_mWPQdbkTIQBA-W8d3WPij4KTyQ,7738
165
+ tests/test_contract.py,sha256=MuL9dI_EJXo7hNiapUPNqmP87LRh3_HluAtOEGEWyus,14116
166
166
  tests/test_db.py,sha256=dmbrupj0qlUeiiycZ2mzMFjf7HrDa6tcqMPY8zpiKIk,5710
167
167
  tests/test_drain_coverage.py,sha256=jtN5tIXzSTlS2IjwLS60azyMYsjFDlSTUa98JM1bMic,6786
168
168
  tests/test_erc20.py,sha256=kNEw1afpm5EbXRNXkjpkBNZIy7Af1nqGlztKH5IWAwU,3074
169
169
  tests/test_gnosis_plugin.py,sha256=XMoHBCTrnVBq9bXYPzMUIrhr95caucMVRxooCjKrzjg,3454
170
- tests/test_keys.py,sha256=BUKaZmnEzs7HzUCiEIMiLd2aJRegLOflOkVlC9s3XXs,17405
171
- tests/test_legacy_wallet.py,sha256=8u83TTJv2zmdfBaLXIhsrbBREqm7EA1KH6J6ZjTyzuc,49604
170
+ tests/test_keys.py,sha256=pxlGlHB-NaTbC8qar093ttdHLLtkM8irVNmCbH2KVf4,17421
171
+ tests/test_legacy_wallet.py,sha256=j7AorUMvuChyxy18y9j8Ux8wpZa2_ZvKutp5obKLLI0,49895
172
172
  tests/test_main.py,sha256=y2xr7HjCt4rHsxm8y6n24FKCteSHPyxC3DFuMcUgX1Y,475
173
173
  tests/test_migration.py,sha256=fYoxzI3KqGh0cPV0bFcbvGrAnKcNlvnwjggG_uD0QGo,1789
174
174
  tests/test_mnemonic.py,sha256=BFtXMMg17uHWh_H-ZwAOn0qzgbUCqL8BRLkgRjzfzxo,7379
@@ -177,19 +177,19 @@ tests/test_models.py,sha256=1bEfPiDVgEdtwFEzwecSPAHjCF8kjOPSMeQExJ7eCJ4,7107
177
177
  tests/test_monitor.py,sha256=P_hF61VMlCX2rh9yu_a6aKhlgXOAcCMGOZRntjcqrd0,7255
178
178
  tests/test_multisend.py,sha256=IvXpwnC5xSDRCyCDGcMdO3L-eQegvdjAzHZB0FoVFUI,2685
179
179
  tests/test_plugin_service.py,sha256=ZEe37kV_sv4Eb04032O1hZIoo9yf5gJo83ks7Grzrng,3767
180
- tests/test_pricing.py,sha256=fTrqBZ4-4W1REl-zy7RN2UKcqYi0UIbmRFB9Ni0iAf0,4685
180
+ tests/test_pricing.py,sha256=ptu_2Csc6d64bIzMMw3TheJge2Kfn05Gs-twz_KmBzg,5276
181
181
  tests/test_rate_limiter.py,sha256=DOIlrBP2AtVFHCpznIoFn2FjFc33emG7M_FffLh4MGE,7314
182
182
  tests/test_reset_tenderly.py,sha256=GVoqbDT3n4_GnlKF5Lx-8ew15jT8I2hIPdTulQDb6dI,7215
183
183
  tests/test_rpc_view.py,sha256=sgZ53KEHl8VGb7WKYa0VI7Cdxbf8JH1SdroHYbWHjfQ,2031
184
- tests/test_safe_coverage.py,sha256=J09TlcPYVms9bqDtVRPs9TnA102obUuApHXsmEDVJsI,5098
185
- tests/test_safe_service.py,sha256=HsUJZxwLCfObdHkCwfesfp8XBiv5gCx9JIJLsHuWgDg,5899
184
+ tests/test_safe_coverage.py,sha256=g9Bdrpkc-Mc8HBjk07lYNRkzxWZiF922uiVLZqhehBE,5093
185
+ tests/test_safe_service.py,sha256=nxDYmGd6p2gGe7BEeMxsqS8CgeJarPofV38HC6Cop44,5770
186
186
  tests/test_service_manager_integration.py,sha256=I_BLUzEKrVTyg_8jqsUK0oFD3aQVPCRJ7z0gY8P-j04,2354
187
187
  tests/test_service_manager_structure.py,sha256=zK506ucCXCBHcjPYKrKEuK1bgq0xsbawyL8Y-wahXf8,868
188
- tests/test_service_transaction.py,sha256=Jzb6oADMrlxNdgVvr3urpYz5NCRNO6h6bUTUQDKeTxQ,6224
189
- tests/test_staking_router.py,sha256=l7IqP9t6eGMgiW0PKW6tmdP2Wb6Fz9diRDjptjzKiLA,2621
188
+ tests/test_service_transaction.py,sha256=SecU2Fy32jLH4lg0CZHNrNkqM1pzPnYB0qZtxqaDnFE,6416
189
+ tests/test_staking_router.py,sha256=e8bI_u24SsORn9bkf4QWOtFoGxi86WTyu_Zl_QnUvpA,2769
190
190
  tests/test_staking_simple.py,sha256=NHyZ1pcVQEJGFiGseC5m6Y9Y6FJGnRIFJUwhd1hAV9g,1138
191
191
  tests/test_tables.py,sha256=1KQHgxuizoOrRxpubDdnzk9iaU5Lwyp3bcWP_hZD5uU,2686
192
- tests/test_transaction_service.py,sha256=NhTuzZnmIbMRbiO1lXutzFNhjdyGDb4QNXbm9U4D7yM,5154
192
+ tests/test_transaction_service.py,sha256=iuRMyOsMS4fRNIhudbW0ovSfA6vGzZpjIApVryOpO3U,5283
193
193
  tests/test_transfer_multisend.py,sha256=PErjNqNwN66TMh4oVa307re64Ucccg1LkXqB0KlkmsI,6677
194
194
  tests/test_transfer_native.py,sha256=cDbb4poV_veIw6eHpokrHe9yUndOjA6rQhrHd_IY3HQ,7445
195
195
  tests/test_transfer_security.py,sha256=gdpC6ybdXQbQgILbAQ0GqjWdwn9AJRNR3B_7TYg0NxI,3617
@@ -198,9 +198,10 @@ tests/test_transfer_swap_unit.py,sha256=v_tld2oF2jFf_qdrCxIPQFlAymQqHT2dcw2Z05qW
198
198
  tests/test_ui_coverage.py,sha256=N-7uhPlKOXCKyS32VjwG3JQRnAS1PoAJxR-InaOZtCQ,2423
199
199
  tests/test_utils.py,sha256=vkP49rYNI8BRzLpWR3WnKdDr8upeZjZcs7Rx0pjbQMo,1292
200
200
  tests/test_workers.py,sha256=MInwdkFY5LdmFB3o1odIaSD7AQZb3263hNafO1De5PE,2793
201
- tools/verify_drain.py,sha256=1On4GmXWETA-CHfFjd7qAqIQuSgKIaEomKKXM7S8wfk,6881
202
- iwa-0.0.2.dist-info/METADATA,sha256=LI8zcRvzlRmevpeYLa1O5KdZbF6GX5L-Xp7QLUcgSI8,7294
203
- iwa-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
204
- iwa-0.0.2.dist-info/entry_points.txt,sha256=nwB6kscrfA7M00pYmL2j-sBH6eF6h2ga9IK1BZxdiyQ,241
205
- iwa-0.0.2.dist-info/top_level.txt,sha256=kedS9cRUbm4JE2wYeabIXilhHjN8KCw0IGbqqqsw0Bs,16
206
- iwa-0.0.2.dist-info/RECORD,,
201
+ tools/create_and_stake_service.py,sha256=1xwy_bJQI1j9yIQ968Oc9Db_F6mk1659LuuZntTASDE,3742
202
+ tools/verify_drain.py,sha256=PkMjblyOOAuQge88FwfEzRtCYeEtJxXhPBmtQYCoQ-8,6743
203
+ iwa-0.0.10.dist-info/METADATA,sha256=GeRcXbxPd8-PCggPGpiLqM-ovGziAsHJp1SzHWMxMao,7295
204
+ iwa-0.0.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
205
+ iwa-0.0.10.dist-info/entry_points.txt,sha256=nwB6kscrfA7M00pYmL2j-sBH6eF6h2ga9IK1BZxdiyQ,241
206
+ iwa-0.0.10.dist-info/top_level.txt,sha256=kedS9cRUbm4JE2wYeabIXilhHjN8KCw0IGbqqqsw0Bs,16
207
+ iwa-0.0.10.dist-info/RECORD,,
@@ -31,8 +31,9 @@ def test_rpc_rotation_exhaustion(mock_web3):
31
31
  # We also need to prevent rotation from working or make rotation also fail
32
32
  # Since we only have 2 RPCs, eventually it will give up.
33
33
 
34
- with pytest.raises(Exception, match="Connection Error"):
35
- chain.with_retry(lambda: chain.web3.eth.get_block("latest"))
34
+ with patch("time.sleep"): # Avoid real exponential backoff delays
35
+ with pytest.raises(Exception, match="Connection Error"):
36
+ chain.with_retry(lambda: chain.web3.eth.get_block("latest"))
36
37
 
37
38
 
38
39
  def test_rate_limiting_backoff(mock_web3):
tests/test_contract.py CHANGED
@@ -195,3 +195,168 @@ def test_extract_events_edge_cases(mock_chain_interface):
195
195
 
196
196
  events = contract.extract_events(receipt)
197
197
  assert len(events) == 0
198
+
199
+
200
+ # =============================================================================
201
+ # RPC ROTATION TESTS - Verify fix for contract.call() re-evaluating on retry
202
+ # =============================================================================
203
+
204
+
205
+ def test_call_reevaluates_contract_on_retry(mock_chain_interface, mock_abi_file):
206
+ """Verify that self.contract is re-evaluated on each retry attempt.
207
+
208
+ This test verifies the fix for the bug where the contract method was
209
+ captured once outside the retry lambda, causing retries to use the
210
+ stale provider after RPC rotation.
211
+ """
212
+ contract = MockContract("0xAddress", "gnosis")
213
+
214
+ # Track how many times web3.eth.contract is called (proxy for contract property access)
215
+ contract_creation_count = [0]
216
+
217
+ def counting_contract_factory(address, abi):
218
+ contract_creation_count[0] += 1
219
+ mock = MagicMock()
220
+ # First call fails, second succeeds
221
+ if contract_creation_count[0] == 1:
222
+ mock.functions.testFunc.return_value.call.side_effect = Exception(
223
+ "429 Too Many Requests"
224
+ )
225
+ else:
226
+ mock.functions.testFunc.return_value.call.return_value = "success"
227
+ return mock
228
+
229
+ mock_chain_interface.web3.eth.contract.side_effect = counting_contract_factory
230
+
231
+ # Implement with_retry that actually retries on 429
232
+ def real_with_retry(fn, max_retries=6, operation_name="operation"):
233
+ for attempt in range(max_retries + 1):
234
+ try:
235
+ return fn()
236
+ except Exception as e:
237
+ if "429" in str(e) and attempt < max_retries:
238
+ continue
239
+ raise
240
+
241
+ mock_chain_interface.with_retry.side_effect = real_with_retry
242
+
243
+ # Execute - should fail first, then succeed
244
+ result = contract.call("testFunc")
245
+
246
+ assert result == "success"
247
+ # KEY ASSERTION: contract property (and thus web3.eth.contract) should be called
248
+ # once per attempt. With the fix, this should be 2. Before the fix, it would be 1.
249
+ assert contract_creation_count[0] == 2, (
250
+ f"Expected contract to be created 2 times (once per retry attempt), "
251
+ f"but was created {contract_creation_count[0]} times. "
252
+ "This suggests the fix for re-evaluating self.contract on retry is not working."
253
+ )
254
+
255
+
256
+ def test_call_uses_fresh_provider_after_rotation(mock_chain_interface, mock_abi_file):
257
+ """Verify that after RPC rotation, the contract uses the new provider.
258
+
259
+ This simulates the scenario where:
260
+ 1. First call fails with 429
261
+ 2. RPC rotates to new provider
262
+ 3. Retry should use the NEW provider, not the old one
263
+ """
264
+ contract = MockContract("0xAddress", "gnosis")
265
+
266
+ # Track which provider version is being used
267
+ provider_versions = []
268
+ current_provider_version = [1] # Mutable to track version changes
269
+
270
+ def mock_contract_factory(address, abi):
271
+ mock = MagicMock()
272
+ # Capture which provider version was used when this contract was created
273
+ mock._provider_version = current_provider_version[0]
274
+ provider_versions.append(current_provider_version[0])
275
+ return mock
276
+
277
+ mock_chain_interface.web3.eth.contract.side_effect = mock_contract_factory
278
+
279
+ # Simulate RPC rotation by incrementing provider version
280
+ def simulate_rotation():
281
+ current_provider_version[0] += 1
282
+ return True
283
+
284
+ mock_chain_interface.rotate_rpc = simulate_rotation
285
+
286
+ # Make with_retry call rotation between attempts
287
+ attempt_count = [0]
288
+
289
+ def mock_with_retry(fn, **kwargs):
290
+ attempt_count[0] += 1
291
+ if attempt_count[0] == 1:
292
+ # First attempt: simulate 429 then rotation
293
+ simulate_rotation()
294
+ # Call again after rotation
295
+ return fn()
296
+ return fn()
297
+
298
+ mock_chain_interface.with_retry.side_effect = mock_with_retry
299
+
300
+ # Execute call (this should access contract property, which creates new contract)
301
+ contract.call("testFunc")
302
+
303
+ # Verify: contract was created at least once with the rotated provider
304
+ # If the fix works, the provider_versions list should show provider version 2
305
+ # (because rotation happened before the successful call)
306
+ assert len(provider_versions) >= 1
307
+ # The last contract should have been created with the rotated provider
308
+ assert provider_versions[-1] == 2, f"Expected provider version 2, got {provider_versions}"
309
+
310
+
311
+ def test_call_with_429_triggers_retry_with_new_contract(mock_chain_interface, mock_abi_file):
312
+ """Integration test: 429 error should trigger retry which uses fresh contract.
313
+
314
+ This test verifies the complete flow:
315
+ 1. Call fails with 429
316
+ 2. with_retry handles it and retries
317
+ 3. The retry uses a fresh contract instance (new provider)
318
+ """
319
+ contract = MockContract("0xAddress", "gnosis")
320
+
321
+ # Create distinct mock contracts for each call
322
+ mock_contract_1 = MagicMock()
323
+ mock_contract_1.functions.testFunc.return_value.call.side_effect = Exception(
324
+ "429 Too Many Requests"
325
+ )
326
+
327
+ mock_contract_2 = MagicMock()
328
+ mock_contract_2.functions.testFunc.return_value.call.return_value = "success_from_rotated_rpc"
329
+
330
+ contracts_returned = [mock_contract_1, mock_contract_2]
331
+ contract_call_count = [0]
332
+
333
+ def mock_contract_factory(address, abi):
334
+ result = contracts_returned[min(contract_call_count[0], len(contracts_returned) - 1)]
335
+ contract_call_count[0] += 1
336
+ return result
337
+
338
+ mock_chain_interface.web3.eth.contract.side_effect = mock_contract_factory
339
+
340
+ # Implement with_retry that actually retries
341
+ def real_with_retry(fn, max_retries=6, operation_name="operation"):
342
+ last_error = None
343
+ for attempt in range(max_retries + 1):
344
+ try:
345
+ return fn()
346
+ except Exception as e:
347
+ last_error = e
348
+ if "429" in str(e) and attempt < max_retries:
349
+ continue # Retry
350
+ raise
351
+ raise last_error
352
+
353
+ mock_chain_interface.with_retry.side_effect = real_with_retry
354
+
355
+ # Execute - should succeed on second attempt with rotated RPC
356
+ result = contract.call("testFunc")
357
+
358
+ assert result == "success_from_rotated_rpc"
359
+ # Verify we created 2 contract instances (one per attempt)
360
+ assert contract_call_count[0] == 2, (
361
+ f"Expected 2 contract creations, got {contract_call_count[0]}"
362
+ )
tests/test_keys.py CHANGED
@@ -67,7 +67,8 @@ def mock_account():
67
67
  def from_key_side_effect(private_key):
68
68
  # 1. Handle the master private key
69
69
  if (
70
- private_key == "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" # gitleaks:allow
70
+ private_key
71
+ == "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" # gitleaks:allow
71
72
  ):
72
73
  addr = "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
73
74
  # 2. Handle our mock key format
@@ -39,6 +39,17 @@ def mock_transaction_service():
39
39
  yield instance
40
40
 
41
41
 
42
+ @pytest.fixture(autouse=True)
43
+ def mock_chain_sleeps():
44
+ """Mock time.sleep in chain modules to speed up tests."""
45
+ with (
46
+ patch("iwa.core.chain.interface.time.sleep"),
47
+ patch("iwa.core.chain.rate_limiter.time.sleep"),
48
+ patch("time.sleep"),
49
+ ):
50
+ yield
51
+
52
+
42
53
  @pytest.fixture
43
54
  def mock_key_storage():
44
55
  with patch("iwa.core.wallet.KeyStorage") as mock:
tests/test_pricing.py CHANGED
@@ -18,6 +18,13 @@ def price_service(mock_secrets):
18
18
  return PriceService()
19
19
 
20
20
 
21
+ @pytest.fixture(autouse=True)
22
+ def clear_cache():
23
+ """Clear global cache before each test to ensure isolation."""
24
+ with patch("iwa.core.pricing._PRICE_CACHE", {}):
25
+ yield
26
+
27
+
21
28
  def test_get_token_price_success(price_service):
22
29
  with patch("iwa.core.pricing.requests.get") as mock_get:
23
30
  mock_get.return_value.status_code = 200
@@ -36,30 +43,36 @@ def test_get_token_price_cached(price_service):
36
43
  # Pre-populate cache
37
44
  from datetime import datetime
38
45
 
39
- price_service.cache["ethereum_eur"] = {"price": 100.0, "timestamp": datetime.now()}
46
+ cache_data = {
47
+ "ethereum_eur": {"price": 100.0, "timestamp": datetime.now()}
48
+ }
40
49
 
41
- with patch("iwa.core.pricing.requests.get") as mock_get:
42
- price = price_service.get_token_price("ethereum", "eur")
43
- assert price == 100.0
44
- mock_get.assert_not_called()
50
+ with patch.dict("iwa.core.pricing._PRICE_CACHE", cache_data):
51
+ with patch("iwa.core.pricing.requests.get") as mock_get:
52
+ price = price_service.get_token_price("ethereum", "eur")
53
+ assert price == 100.0
54
+ mock_get.assert_not_called()
45
55
 
46
56
 
47
57
  def test_get_token_price_cache_expired(price_service):
48
58
  # Pre-populate expired cache
49
59
  from datetime import datetime
50
60
 
51
- price_service.cache["ethereum_eur"] = {
52
- "price": 100.0,
53
- "timestamp": datetime.now() - timedelta(minutes=10),
61
+ cache_data = {
62
+ "ethereum_eur": {
63
+ "price": 100.0,
64
+ "timestamp": datetime.now() - timedelta(minutes=60), # > 30 min TTL
65
+ }
54
66
  }
55
67
 
56
- with patch("iwa.core.pricing.requests.get") as mock_get:
57
- mock_get.return_value.status_code = 200
58
- mock_get.return_value.json.return_value = {"ethereum": {"eur": 200.0}}
68
+ with patch.dict("iwa.core.pricing._PRICE_CACHE", cache_data):
69
+ with patch("iwa.core.pricing.requests.get") as mock_get:
70
+ mock_get.return_value.status_code = 200
71
+ mock_get.return_value.json.return_value = {"ethereum": {"eur": 200.0}}
59
72
 
60
- price = price_service.get_token_price("ethereum", "eur")
61
- assert price == 200.0
62
- mock_get.assert_called_once()
73
+ price = price_service.get_token_price("ethereum", "eur")
74
+ assert price == 200.0
75
+ mock_get.assert_called_once()
63
76
 
64
77
 
65
78
  def test_get_token_price_api_error(price_service):
@@ -84,7 +97,9 @@ def test_get_token_price_rate_limit():
84
97
  with patch("iwa.core.pricing.secrets") as mock_secrets:
85
98
  mock_secrets.coingecko_api_key = None
86
99
 
100
+ # Need to re-instantiate or patch secrets on instance since it's read in __init__
87
101
  service = PriceService()
102
+ service.api_key = None
88
103
 
89
104
  with patch("iwa.core.pricing.requests.get") as mock_get, patch("time.sleep"):
90
105
  # Return 429 for all attempts
@@ -94,7 +109,7 @@ def test_get_token_price_rate_limit():
94
109
  price = service.get_token_price("ethereum", "eur")
95
110
 
96
111
  assert price is None
97
- # Should have tried max_retries + 1 times
112
+ # Should have tried max_retries + 1 times (3 total)
98
113
  assert mock_get.call_count == 3
99
114
 
100
115
 
@@ -106,6 +121,7 @@ def test_get_token_price_rate_limit_then_success():
106
121
  mock_secrets.coingecko_api_key = None
107
122
 
108
123
  service = PriceService()
124
+ service.api_key = None
109
125
 
110
126
  with patch("iwa.core.pricing.requests.get") as mock_get, patch("time.sleep"):
111
127
  # First call returns 429, second succeeds
@@ -130,6 +146,7 @@ def test_get_token_price_no_api_key():
130
146
  mock_secrets.coingecko_api_key = None
131
147
 
132
148
  service = PriceService()
149
+ service.api_key = None
133
150
 
134
151
  with patch("iwa.core.pricing.requests.get") as mock_get:
135
152
  mock_get.return_value.status_code = 200
@@ -56,7 +56,7 @@ def test_execute_safe_transaction_success(safe_service, mock_deps):
56
56
  mock_safe_instance = mock_safe_multisig_cls.return_value
57
57
  mock_safe_tx = MagicMock()
58
58
  mock_safe_instance.build_tx.return_value = mock_safe_tx
59
- mock_safe_tx.tx_hash.hex.return_value = "0xTxHash"
59
+ mock_safe_tx.tx_hash.hex.return_value = "TxHash"
60
60
 
61
61
  # Execute
62
62
  tx_hash = safe_service.execute_safe_transaction(safe_address, to_address, value, chain_name)
@@ -90,7 +90,7 @@ def test_get_sign_and_execute_callback(safe_service, mock_deps):
90
90
 
91
91
  # Test executing callback
92
92
  mock_safe_tx = MagicMock()
93
- mock_safe_tx.tx_hash.hex.return_value = "0xTxHash"
93
+ mock_safe_tx.tx_hash.hex.return_value = "TxHash"
94
94
 
95
95
  result = callback(mock_safe_tx)
96
96
 
@@ -119,7 +119,7 @@ def test_redeploy_safes(safe_service, mock_deps):
119
119
 
120
120
  mock_deps["key_storage"].accounts = {"0xSafe1": account1}
121
121
 
122
- with patch("iwa.core.services.safe.secrets") as mock_settings:
122
+ with patch("iwa.core.chain.models.secrets") as mock_settings:
123
123
  mock_settings.gnosis_rpc.get_secret_value.return_value = "http://rpc"
124
124
 
125
125
  with patch("iwa.core.services.safe.EthereumClient") as mock_eth_client:
@@ -53,18 +53,16 @@ def mock_dependencies():
53
53
  patch("iwa.core.services.safe.EthereumClient") as mock_client,
54
54
  patch("iwa.core.services.safe.Safe") as mock_safe,
55
55
  patch("iwa.core.services.safe.ProxyFactory") as mock_proxy_factory,
56
- patch("iwa.core.services.safe.secrets") as mock_secrets,
57
56
  patch("iwa.core.services.safe.log_transaction") as mock_log,
58
57
  patch("iwa.core.services.safe.get_safe_master_copy_address") as mock_master,
59
58
  patch("iwa.core.services.safe.get_safe_proxy_factory_address") as mock_factory,
59
+ patch("time.sleep"), # Avoid any retry delays
60
60
  ):
61
- mock_secrets.gnosis_rpc.get_secret_value.return_value = "http://rpc"
62
-
63
61
  # Setup Safe creation return
64
62
  mock_create_tx = MagicMock()
65
63
  # Valid Checksum Address - New Safe (Matches Pydantic output)
66
64
  mock_create_tx.contract_address = "0xbEC49fa140ACaa83533f900357DCD37866d50618"
67
- mock_create_tx.tx_hash.hex.return_value = "0xTxHash"
65
+ mock_create_tx.tx_hash.hex.return_value = "TxHash"
68
66
 
69
67
  mock_safe.create.return_value = mock_create_tx
70
68
 
@@ -72,7 +70,7 @@ def mock_dependencies():
72
70
  mock_deploy_tx = MagicMock()
73
71
  # Valid checksum address - Salted Safe
74
72
  mock_deploy_tx.contract_address = "0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5"
75
- mock_deploy_tx.tx_hash.hex.return_value = "0xTxHashSalted"
73
+ mock_deploy_tx.tx_hash.hex.return_value = "TxHashSalted"
76
74
 
77
75
  mock_proxy_factory.return_value.deploy_proxy_contract_with_nonce.return_value = (
78
76
  mock_deploy_tx
@@ -104,7 +102,6 @@ def mock_dependencies():
104
102
  "client": mock_client,
105
103
  "safe": mock_safe,
106
104
  "proxy_factory": mock_proxy_factory,
107
- "secrets": mock_secrets,
108
105
  "log": mock_log,
109
106
  "master": mock_master,
110
107
  "factory": mock_factory,
@@ -17,6 +17,10 @@ def mock_chain_interfaces():
17
17
  gnosis_interface.chain = mock_chain
18
18
  gnosis_interface.web3 = MagicMock()
19
19
  instance.get.return_value = gnosis_interface
20
+
21
+ # Mock with_retry to execute the operation
22
+ gnosis_interface.with_retry.side_effect = lambda op, **kwargs: op()
23
+
20
24
  yield instance
21
25
 
22
26
 
@@ -115,8 +119,8 @@ def test_sign_and_send_max_retries_exhausted(
115
119
  # Should fail after max retries
116
120
  assert success is False
117
121
  assert receipt == {} # Returns empty dict on failure
118
- # Should have tried 3 times (max_retries)
119
- assert chain_interface.web3.eth.send_raw_transaction.call_count == 3
122
+ # Should have tried 10 times (max_retries)
123
+ assert chain_interface.web3.eth.send_raw_transaction.call_count == 10
120
124
 
121
125
 
122
126
  def test_sign_and_send_transaction_reverted(
@@ -170,7 +174,8 @@ def test_sign_and_send_signer_not_found(
170
174
  # Signing raises ValueError for unknown account
171
175
  mock_key_storage.sign_transaction.side_effect = ValueError("Account not found")
172
176
 
173
- success, receipt = transaction_service.sign_and_send(tx, "unknown_signer")
177
+ with patch("time.sleep"): # Avoid real retry delays
178
+ success, receipt = transaction_service.sign_and_send(tx, "unknown_signer")
174
179
 
175
180
  assert success is False
176
181
  assert receipt == {} # Returns empty dict on failure