iwa 0.0.14__py3-none-any.whl → 0.0.16__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.
@@ -19,7 +19,18 @@ logger = configure_logger()
19
19
  ERROR_SELECTOR = "0x08c379a0" # Error(string)
20
20
  PANIC_SELECTOR = "0x4e487b71" # Panic(uint256)
21
21
 
22
+ # Global cache for ABIs and error selectors to avoid redundant disk I/O and parsing
23
+ # Format: {abi_path: {"abi": [...], "selectors": {...}}}
24
+ _ABI_CACHE: Dict[str, Dict[str, Any]] = {}
25
+
26
+
27
+ def clear_abi_cache() -> None:
28
+ """Clear the global ABI cache (mainly for testing)."""
29
+ global _ABI_CACHE
30
+ _ABI_CACHE = {}
31
+
22
32
  # Panic codes (from Solidity)
33
+ # ... (rest of PANIC_CODES) ...
23
34
  PANIC_CODES = {
24
35
  0x00: "Generic compiler inserted panic",
25
36
  0x01: "Assert failed",
@@ -46,16 +57,25 @@ class ContractInstance:
46
57
  self.abi = None
47
58
  self.chain_interface = ChainInterfaces().get(chain_name)
48
59
 
49
- with open(self.abi_path, "r", encoding="utf-8") as abi_file:
50
- contract_abi = json.load(abi_file)
60
+ # Check global cache first
61
+ cache_key = str(self.abi_path)
62
+ if cache_key in _ABI_CACHE:
63
+ self.abi = _ABI_CACHE[cache_key]["abi"]
64
+ self.error_selectors = _ABI_CACHE[cache_key]["selectors"]
65
+ else:
66
+ with open(self.abi_path, "r", encoding="utf-8") as abi_file:
67
+ contract_abi = json.load(abi_file)
68
+
69
+ if isinstance(contract_abi, dict) and "abi" in contract_abi:
70
+ self.abi = contract_abi.get("abi")
71
+ else:
72
+ self.abi = contract_abi
51
73
 
52
- if isinstance(contract_abi, dict) and "abi" in contract_abi:
53
- self.abi = contract_abi.get("abi")
54
- else:
55
- self.abi = contract_abi
74
+ self.error_selectors = self.load_error_selectors()
75
+ # Store in global cache
76
+ _ABI_CACHE[cache_key] = {"abi": self.abi, "selectors": self.error_selectors}
56
77
 
57
78
  self._contract_cache = None
58
- self.error_selectors = self.load_error_selectors()
59
79
 
60
80
  @property
61
81
  def contract(self) -> Contract:
iwa/core/pricing.py CHANGED
@@ -12,12 +12,14 @@ from iwa.core.secrets import secrets
12
12
  # Global cache shared across all PriceService instances
13
13
  _PRICE_CACHE: Dict[str, Dict] = {}
14
14
  _CACHE_TTL = timedelta(minutes=30)
15
+ _NEGATIVE_CACHE_TTL = timedelta(minutes=5)
15
16
 
16
17
 
17
18
  class PriceService:
18
19
  """Service to fetch token prices from CoinGecko."""
19
20
 
20
21
  BASE_URL = "https://api.coingecko.com/api/v3"
22
+ DEMO_URL = "https://demo-api.coingecko.com/api/v3"
21
23
 
22
24
  def __init__(self):
23
25
  """Initialize PriceService."""
@@ -41,15 +43,16 @@ class PriceService:
41
43
  """
42
44
  cache_key = f"{token_id}_{vs_currency}"
43
45
 
44
- # Check global cache
46
+ # Check global cache (including negative cache)
45
47
  if cache_key in _PRICE_CACHE:
46
48
  entry = _PRICE_CACHE[cache_key]
47
- if datetime.now() - entry["timestamp"] < _CACHE_TTL:
49
+ ttl = _CACHE_TTL if entry["price"] is not None else _NEGATIVE_CACHE_TTL
50
+ if datetime.now() - entry["timestamp"] < ttl:
48
51
  return entry["price"]
49
52
 
50
53
  price = self._fetch_price_from_api(token_id, vs_currency)
51
- if price is not None:
52
- _PRICE_CACHE[cache_key] = {"price": price, "timestamp": datetime.now()}
54
+ # We always cache, even if price is None (negative caching)
55
+ _PRICE_CACHE[cache_key] = {"price": price, "timestamp": datetime.now()}
53
56
  return price
54
57
 
55
58
  def _fetch_price_from_api(self, token_id: str, vs_currency: str) -> Optional[float]:
@@ -57,7 +60,10 @@ class PriceService:
57
60
  max_retries = 2
58
61
  for attempt in range(max_retries + 1):
59
62
  try:
60
- url = f"{self.BASE_URL}/simple/price"
63
+ # Use demo URL if API key is present, otherwise standard URL
64
+ # NOTE: Demo URL is significantly more reliable for demo keys
65
+ base_url = self.DEMO_URL if self.api_key else self.BASE_URL
66
+ url = f"{base_url}/simple/price"
61
67
  params = {"ids": token_id, "vs_currencies": vs_currency}
62
68
  headers = {}
63
69
  if self.api_key:
@@ -69,6 +75,8 @@ class PriceService:
69
75
  logger.warning("CoinGecko API key invalid (401). Retrying without key...")
70
76
  self.api_key = None
71
77
  headers.pop("x-cg-demo-api-key", None)
78
+ # Re-run with base URL
79
+ url = f"{self.BASE_URL}/simple/price"
72
80
  response = requests.get(url, params=params, headers=headers, timeout=10)
73
81
 
74
82
  if response.status_code == 429:
@@ -87,13 +95,16 @@ class PriceService:
87
95
  if token_id in data and vs_currency in data[token_id]:
88
96
  return float(data[token_id][vs_currency])
89
97
 
90
- logger.warning(
98
+ # If we got response but price not found, it's likely a wrong ID
99
+ logger.debug(
91
100
  f"Price for {token_id} in {vs_currency} not found in response: {data}"
92
101
  )
93
102
  return None
94
103
 
95
104
  except Exception as e:
96
- logger.error(f"Failed to fetch price for {token_id} (Attempt {attempt + 1}): {e}")
105
+ # Only log error on last attempt to avoid spamming
106
+ if attempt == max_retries:
107
+ logger.error(f"Failed to fetch price for {token_id}: {e}")
97
108
  if attempt < max_retries:
98
109
  time.sleep(1)
99
110
  continue
@@ -51,11 +51,21 @@ OLAS_CONTRACTS: Dict[str, Dict[str, EthereumAddress]] = {
51
51
  "0xa45E64d13A30a51b91ae0eb182e88a40e9b18eD8"
52
52
  ),
53
53
  "OLAS_SERVICE_MANAGER": EthereumAddress("0x068a4f0946cF8c7f9C1B58a3b5243Ac8843bf473"),
54
- # Legacy mech - used by TRADER staking contracts for liveness
54
+ # Legacy mech - used by NON-MM TRADER staking contracts (e.g., "Expert X (Yk OLAS)")
55
+ # Activity checker calls agentMech.getRequestsCount(multisig) to count requests.
55
56
  "OLAS_MECH": EthereumAddress("0x77af31De935740567Cf4fF1986D04B2c964A786a"),
56
- # NEW Marketplace (v2) - no staking support yet
57
+ # NEW Marketplace (v2) - used by MM staking contracts (e.g., "Expert X MM (Yk OLAS)")
58
+ # Services staked in "Expert X MM" contracts MUST use marketplace requests.
59
+ # Legacy mech requests will NOT be counted by the activity checker.
57
60
  "OLAS_MECH_MARKETPLACE": EthereumAddress("0x735FAAb1c4Ec41128c367AFb5c3baC73509f70bB"),
58
- # OLD Marketplace (v1) - used by Pearl Beta Mech Marketplace staking
61
+ # Default priority mech for marketplace requests (from olas-operate-middleware)
62
+ # This is the mech that will process requests sent via the marketplace.
63
+ # Source: https://github.com/valory-xyz/olas-operate-middleware/blob/main/operate/ledger/profiles.py
64
+ # DEFAULT_PRIORITY_MECH["0x735FAAb1c..."] = ("0xC05e7412...", 2182)
65
+ "OLAS_MECH_MARKETPLACE_PRIORITY": EthereumAddress(
66
+ "0xC05e7412439bD7e91730a6880E18d5D5873F632C"
67
+ ),
68
+ # OLD Marketplace (v1) - legacy, kept for reference
59
69
  "OLAS_MECH_MARKETPLACE_OLD": EthereumAddress("0x4554fE75c1f5576c1d7F765B2A036c199Adae329"),
60
70
  },
61
71
  "ethereum": {
@@ -43,14 +43,27 @@ class ActivityCheckerContract(ContractInstance):
43
43
  """
44
44
  super().__init__(address, chain_name=chain_name)
45
45
 
46
- # Get the mech address this checker tracks
47
- agent_mech_function = getattr(self.contract.functions, "agentMech", None)
48
- self.agent_mech = (
49
- agent_mech_function().call() if agent_mech_function else DEFAULT_MECH_CONTRACT_ADDRESS
50
- )
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
51
61
 
52
62
  # Get liveness ratio (requests per second * 1e18)
53
- self.liveness_ratio = self.contract.functions.livenessRatio().call()
63
+ try:
64
+ self.liveness_ratio = self.contract.functions.livenessRatio().call()
65
+ except Exception:
66
+ self.liveness_ratio = 0
54
67
 
55
68
  def get_multisig_nonces(self, multisig: EthereumAddress) -> Tuple[int, int]:
56
69
  """Get the nonces for a multisig address.
@@ -51,6 +51,10 @@ class ServiceManagerBase:
51
51
 
52
52
  def _init_contracts(self, chain_name: str) -> None:
53
53
  """Initialize contracts for the given chain."""
54
+ # OPTIMIZATION: Skip if already initialized for this chain
55
+ if getattr(self, "chain_name", None) == chain_name.lower() and hasattr(self, "registry"):
56
+ return
57
+
54
58
  chain_interface = ChainInterfaces().get(chain_name)
55
59
 
56
60
  # Get protocol contracts from plugin-local constants
@@ -63,9 +67,9 @@ class ServiceManagerBase:
63
67
 
64
68
  self.registry = ServiceRegistryContract(registry_address, chain_name=chain_name)
65
69
  self.manager = ServiceManagerContract(manager_address, chain_name=chain_name)
66
- logger.info(f"[SM-INIT] ServiceManager initialized. Chain: {chain_name}")
67
- logger.info(f"[SM-INIT] Registry Address: {self.registry.address}")
68
- logger.info(f"[SM-INIT] Manager Address: {self.manager.address}")
70
+ logger.debug(f"[SM-INIT] ServiceManager initialized. Chain: {chain_name}")
71
+ logger.debug(f"[SM-INIT] Registry Address: {self.registry.address}")
72
+ logger.debug(f"[SM-INIT] Manager Address: {self.manager.address}")
69
73
  self.chain_interface = chain_interface
70
74
  self.chain_name = chain_name.lower()
71
75
 
@@ -1,4 +1,29 @@
1
- """Mech manager mixin."""
1
+ """Mech manager mixin.
2
+
3
+ This module handles sending mech requests for OLAS services. There are TWO
4
+ distinct flows for mech requests, and the correct one MUST be used based on
5
+ the service's staking contract:
6
+
7
+ 1. **Legacy Mech Flow** (use_marketplace=False):
8
+ - Sends requests directly to the legacy mech contract (0x77af31De...)
9
+ - Used by NON-MM staking contracts (e.g., "Expert X (Yk OLAS)")
10
+ - Activity checker calls `agentMech.getRequestsCount(multisig)` to count
11
+
12
+ 2. **Marketplace Flow** (use_marketplace=True):
13
+ - Sends requests via MechMarketplace contract (0x735FAAb1c...)
14
+ - Used by MM staking contracts (e.g., "Expert X MM (Yk OLAS)")
15
+ - Activity checker calls `mechMarketplace.mapRequestCounts(multisig)` to count
16
+ - Requires a priority_mech that is registered on the marketplace
17
+ - Default priority_mech from olas-operate-middleware: 0xC05e7412...
18
+
19
+ **IMPORTANT**: If a service is staked in an MM contract but sends legacy mech
20
+ requests, those requests will NOT be counted by the activity checker, and the
21
+ service will not receive staking rewards.
22
+
23
+ The `get_marketplace_config()` method automatically detects which flow to use
24
+ by checking if the staking contract's activity checker has a `mechMarketplace`
25
+ field set to a non-zero address.
26
+ """
2
27
 
3
28
  from typing import Optional
4
29
 
@@ -17,12 +42,60 @@ from iwa.plugins.olas.contracts.mech_marketplace import MechMarketplaceContract
17
42
  class MechManagerMixin:
18
43
  """Mixin for Mech interactions."""
19
44
 
45
+ def get_marketplace_config(self) -> tuple:
46
+ """Check if current service requires marketplace mech requests.
47
+
48
+ Queries the staking contract's activityChecker to determine if it
49
+ tracks marketplace requests.
50
+
51
+ Returns:
52
+ Tuple of (use_marketplace, marketplace_address, priority_mech):
53
+ - use_marketplace: True if marketplace requests are required.
54
+ - marketplace_address: Address of the mech marketplace (if applicable).
55
+ - priority_mech: Address of the priority mech (if applicable).
56
+
57
+ """
58
+ from iwa.core.constants import ZERO_ADDRESS
59
+
60
+ if not self.service or not self.service.staking_contract_address:
61
+ return (False, None, None)
62
+
63
+ try:
64
+ # Get staking contract with its activity checker
65
+ from iwa.plugins.olas.contracts.staking import StakingContract
66
+
67
+ staking = StakingContract(
68
+ self.service.staking_contract_address, chain_name=self.chain_name
69
+ )
70
+
71
+ # StakingContract has activity_checker attribute set in __init__
72
+ checker = staking.activity_checker
73
+
74
+ if checker.mech_marketplace and checker.mech_marketplace != ZERO_ADDRESS:
75
+ # Use the default marketplace priority mech from constants
76
+ from iwa.plugins.olas.constants import OLAS_CONTRACTS
77
+
78
+ protocol_contracts = OLAS_CONTRACTS.get(self.chain_name, {})
79
+ priority_mech = protocol_contracts.get("OLAS_MECH_MARKETPLACE_PRIORITY")
80
+
81
+ logger.info(
82
+ f"[MECH] Service {self.service.service_id} requires marketplace requests "
83
+ f"(marketplace={checker.mech_marketplace}, priority_mech={priority_mech})"
84
+ )
85
+ return (True, checker.mech_marketplace, priority_mech)
86
+
87
+ return (False, None, None)
88
+
89
+ except Exception as e:
90
+ logger.debug(f"[MECH] Failed to detect marketplace config: {e}")
91
+ return (False, None, None)
92
+
20
93
  def send_mech_request(
21
94
  self,
22
95
  data: bytes,
23
96
  value: Optional[int] = None,
24
97
  mech_address: Optional[str] = None,
25
- use_marketplace: bool = False,
98
+ use_marketplace: Optional[bool] = None,
26
99
  use_new_abi: bool = False,
27
100
  priority_mech: Optional[str] = None,
28
101
  max_delivery_rate: Optional[int] = None,
@@ -37,6 +110,7 @@ class MechManagerMixin:
37
110
  value: Payment value in wei. For marketplace, should match mech's maxDeliveryRate.
38
111
  mech_address: Address of the Mech contract (for legacy/direct flow).
39
112
  use_marketplace: Whether to use the Mech Marketplace flow.
113
+ If None, auto-detects based on staking contract.
40
114
  use_new_abi: Whether to use new ABI for legacy flow.
41
115
  priority_mech: Priority mech address (required for marketplace).
42
116
  max_delivery_rate: Max delivery rate in wei (for marketplace). If None, uses value.
@@ -59,6 +133,14 @@ class MechManagerMixin:
59
133
  logger.error(f"Service {service_id} has no multisig address")
60
134
  return None
61
135
 
136
+ # Auto-detect marketplace requirement if not explicitly specified
137
+ if use_marketplace is None:
138
+ use_marketplace, detected_marketplace, detected_priority_mech = (
139
+ self.get_marketplace_config()
140
+ )
141
+ if use_marketplace:
142
+ priority_mech = priority_mech or detected_priority_mech
143
+
62
144
  if use_marketplace:
63
145
  return self._send_marketplace_mech_request(
64
146
  data=data,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iwa
3
- Version: 0.0.14
3
+ Version: 0.0.16
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
@@ -10,7 +10,7 @@ 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=B8dAUszv9t5EqvXQ7gh9CNBqlMLR598-c6Pv3NqtpO4,3561
13
+ iwa/core/pricing.py,sha256=U-vIjJwzh6O7MZTAFZx6mFFxaiLcwn6PnsZc4g0z4IQ,4293
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
@@ -25,7 +25,7 @@ iwa/core/chain/manager.py,sha256=cFEzh6pK5OyVhjhpeMAqhc9RnRDQR1DjIGiGKp-FXBI,115
25
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=Gx9L9wRithMALhdnQeF-0JA30_ZRHzlczAGiJXEfU5k,11766
28
+ iwa/core/contracts/contract.py,sha256=N2I4-38O_6awqVrTEdJFh4XhsMbeu2JtFvkboUC4ZMg,12517
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
@@ -56,12 +56,12 @@ iwa/plugins/gnosis/cow/types.py,sha256=-9VRiFhAkmN1iIJ95Pg7zLFSeXtkkW00sl13usxi3
56
56
  iwa/plugins/gnosis/tests/test_cow.py,sha256=iVy5ockMIcPZWsX4WGXU91DhBsYEZ5NOxtFzAQ2sK3o,8440
57
57
  iwa/plugins/gnosis/tests/test_safe.py,sha256=pw1zrYvAiVtmPIU5k7BtOQpDNAQTSTrLIaeljCjSahc,3216
58
58
  iwa/plugins/olas/__init__.py,sha256=_NhBczzM61fhGYwGhnWfEeL8Jywyy_730GASe2BxzeQ,106
59
- iwa/plugins/olas/constants.py,sha256=mFUMDB46WbM9Y8sw7c-i9TRyJVrDNZXdCXa64M5tI6A,6608
59
+ iwa/plugins/olas/constants.py,sha256=nbthnzu90xgfbXOU2GwovbNmDLUBNDSZBrEQ02H-5wk,7381
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
63
  iwa/plugins/olas/plugin.py,sha256=S_vnvZ02VdVWD7N5kp7u5JIRQ2JLtfwGDZ7OHkAN0M8,9390
64
- iwa/plugins/olas/contracts/activity_checker.py,sha256=UDbCgFOyuEhHf1qoUwCUdLc8oK8ksuAYE-ZgKrMBnwg,3415
64
+ iwa/plugins/olas/contracts/activity_checker.py,sha256=PTLvsFdi3PdsFMxRVcXfwlQMRyJYHIzrHf3OaPVtFqU,3943
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
@@ -79,10 +79,10 @@ iwa/plugins/olas/contracts/abis/staking_token.json,sha256=cuUOmi1s4Z6VSIX0an_IxK
79
79
  iwa/plugins/olas/scripts/test_full_mech_flow.py,sha256=id9IxC06FOKwexgwsG5nbsTB2rQLlIq5a5soMvK0IgY,9021
80
80
  iwa/plugins/olas/scripts/test_simple_lifecycle.py,sha256=8T50tOZx3afeECSfCNAb0rAHNtYOsBaeXlMwKXElCk8,2099
81
81
  iwa/plugins/olas/service_manager/__init__.py,sha256=GXiThMEY3nPgHUl1i-DLrF4h96z9jPxxI8Jepo2E1PM,1926
82
- iwa/plugins/olas/service_manager/base.py,sha256=V4o71ZYUSc_GzM-RWocaAGsrqI-fa5V7CcyloVpcjwE,4666
82
+ iwa/plugins/olas/service_manager/base.py,sha256=CCTH7RiYtgyFwRszrMLxNf1rNM_6leWHuJJmse4m2wI,4854
83
83
  iwa/plugins/olas/service_manager/drain.py,sha256=IS7YYKuQdkULcNdxfHVzjcq95pXKdpajolzLL78u4jc,12430
84
84
  iwa/plugins/olas/service_manager/lifecycle.py,sha256=DIB6yrP0VPICu6558uQJuFp2sgrA66iVNTzZVUUowGw,47159
85
- iwa/plugins/olas/service_manager/mech.py,sha256=72-tEap-aYd0gebcH6y_De1SNeL6OrXn7sWuv_Ok_-Y,12224
85
+ iwa/plugins/olas/service_manager/mech.py,sha256=Rl4FJIYrT9L4gzTd7-7xBZaxBEHWFyMmN5TuSNfEdZc,15964
86
86
  iwa/plugins/olas/service_manager/staking.py,sha256=Z9GzlPfY7qSSjc9xPhWvb9qywxu_7OB3Gc1eBli07pY,28058
87
87
  iwa/plugins/olas/tests/conftest.py,sha256=4vM7EI00SrTGyeP0hNzsGSQHEj2-iznVgzlNh2_OGfo,739
88
88
  iwa/plugins/olas/tests/test_importer.py,sha256=i9LKov7kNRECB3hmRnhKBwcfx3uxtjWe4BB77bOOpeo,4282
@@ -150,7 +150,7 @@ iwa/web/tests/test_web_endpoints.py,sha256=C264MH-CTyDW4GLUrTXBgLJKUk4-89pFAScBd
150
150
  iwa/web/tests/test_web_olas.py,sha256=0CVSsrncOeJ3x0ECV7mVLQV_CXZRrOqGiVjgLIi6hZ8,16308
151
151
  iwa/web/tests/test_web_swap.py,sha256=7A4gBJFL01kIXPtW1E1J17SCsVc_0DmUn-R8kKrnnVA,2974
152
152
  iwa/web/tests/test_web_swap_coverage.py,sha256=zGNrzlhZ_vWDCvWmLcoUwFgqxnrp_ACbo49AtWBS_Kw,5584
153
- iwa-0.0.14.dist-info/licenses/LICENSE,sha256=eIubm_IlBHPYRQlLNZKbBNKhJUUP3JH0A2miZUhAVfI,1078
153
+ iwa-0.0.16.dist-info/licenses/LICENSE,sha256=eIubm_IlBHPYRQlLNZKbBNKhJUUP3JH0A2miZUhAVfI,1078
154
154
  tests/legacy_cow.py,sha256=oOkZvIxL70ReEoD9oHQbOD5GpjIr6AGNHcOCgfPlerU,8389
155
155
  tests/legacy_safe.py,sha256=AssM2g13E74dNGODu_H0Q0y412lgqsrYnEzI97nm_Ts,2972
156
156
  tests/legacy_transaction_retry_logic.py,sha256=D9RqZ7DBu61Xr2djBAodU2p9UE939LL-DnQXswX5iQk,1497
@@ -163,7 +163,7 @@ tests/test_chain.py,sha256=z3Mo9mHNQZ0aXSlrHUcdtNgGqsNyOwWYQAmmKa_dqiM,17221
163
163
  tests/test_chain_interface.py,sha256=Wu0q0sREtmYBp7YvWrBIrrSTtqeQj18oJp2VmMUEMec,8312
164
164
  tests/test_chain_interface_coverage.py,sha256=fvrVvw8-DMwdsSFKQHUhpbfutrVRxnnTc-tjB7Bb-jo,3327
165
165
  tests/test_cli.py,sha256=WW6EDeHLws5-BqFNOy11pH_D5lttuyspD5hrDCFpR0Q,3968
166
- tests/test_contract.py,sha256=EP0dJzViJn0Hbqw3hkCN6nygx5O1v2-DH7Lu8iDBACM,14235
166
+ tests/test_contract.py,sha256=tApHAxsfKGawYJWA9PhTNrOZUE0VVAq79ruIe3KxeWY,14412
167
167
  tests/test_db.py,sha256=dmbrupj0qlUeiiycZ2mzMFjf7HrDa6tcqMPY8zpiKIk,5710
168
168
  tests/test_drain_coverage.py,sha256=jtN5tIXzSTlS2IjwLS60azyMYsjFDlSTUa98JM1bMic,6786
169
169
  tests/test_erc20.py,sha256=kNEw1afpm5EbXRNXkjpkBNZIy7Af1nqGlztKH5IWAwU,3074
@@ -202,8 +202,8 @@ tests/test_utils.py,sha256=vkP49rYNI8BRzLpWR3WnKdDr8upeZjZcs7Rx0pjbQMo,1292
202
202
  tests/test_workers.py,sha256=MInwdkFY5LdmFB3o1odIaSD7AQZb3263hNafO1De5PE,2793
203
203
  tools/create_and_stake_service.py,sha256=1xwy_bJQI1j9yIQ968Oc9Db_F6mk1659LuuZntTASDE,3742
204
204
  tools/verify_drain.py,sha256=PkMjblyOOAuQge88FwfEzRtCYeEtJxXhPBmtQYCoQ-8,6743
205
- iwa-0.0.14.dist-info/METADATA,sha256=7Ymm-F6pLqq7zyN1r-5FQAfLhhtEDzjpPpgGuMf1-Vo,7295
206
- iwa-0.0.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
207
- iwa-0.0.14.dist-info/entry_points.txt,sha256=nwB6kscrfA7M00pYmL2j-sBH6eF6h2ga9IK1BZxdiyQ,241
208
- iwa-0.0.14.dist-info/top_level.txt,sha256=kedS9cRUbm4JE2wYeabIXilhHjN8KCw0IGbqqqsw0Bs,16
209
- iwa-0.0.14.dist-info/RECORD,,
205
+ iwa-0.0.16.dist-info/METADATA,sha256=4uDBeClkmX50DO4TFAPhy0DpCiiLp4zwDFsrIzV6SwA,7295
206
+ iwa-0.0.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
207
+ iwa-0.0.16.dist-info/entry_points.txt,sha256=nwB6kscrfA7M00pYmL2j-sBH6eF6h2ga9IK1BZxdiyQ,241
208
+ iwa-0.0.16.dist-info/top_level.txt,sha256=kedS9cRUbm4JE2wYeabIXilhHjN8KCw0IGbqqqsw0Bs,16
209
+ iwa-0.0.16.dist-info/RECORD,,
tests/test_contract.py CHANGED
@@ -4,7 +4,15 @@ from unittest.mock import MagicMock, mock_open, patch
4
4
  import pytest
5
5
  from web3.exceptions import ContractCustomError
6
6
 
7
- from iwa.core.contracts.contract import ContractInstance
7
+ from iwa.core.contracts.contract import ContractInstance, clear_abi_cache
8
+
9
+
10
+ @pytest.fixture(autouse=True)
11
+ def clean_abi_cache():
12
+ """Clear global ABI cache before each test."""
13
+ clear_abi_cache()
14
+ yield
15
+ clear_abi_cache()
8
16
 
9
17
 
10
18
  @pytest.fixture
File without changes