iwa 0.0.17__py3-none-any.whl → 0.0.19__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.
@@ -10,8 +10,6 @@ requests relative to the time elapsed since the last checkpoint.
10
10
 
11
11
  from typing import Tuple
12
12
 
13
- from web3 import Web3
14
-
15
13
  from iwa.core.constants import DEFAULT_MECH_CONTRACT_ADDRESS
16
14
  from iwa.core.types import EthereumAddress
17
15
  from iwa.plugins.olas.contracts.base import OLAS_ABI_PATH, ContractInstance
@@ -70,9 +68,6 @@ class ActivityCheckerContract(ContractInstance):
70
68
  def get_multisig_nonces(self, multisig: EthereumAddress) -> Tuple[int, int]:
71
69
  """Get the nonces for a multisig address.
72
70
 
73
- This method reads directly from the source contracts to ensure fresh data
74
- and compatibility with Legacy/MM contracts.
75
-
76
71
  Args:
77
72
  multisig: The multisig address to check.
78
73
 
@@ -82,52 +77,8 @@ class ActivityCheckerContract(ContractInstance):
82
77
  - mech_requests_count: Total mech requests made
83
78
 
84
79
  """
85
- # 1. Get Safe Nonce
86
- safe_nonce = 0
87
- try:
88
- # Minimal ABI for Safe nonce
89
- safe_abi = [{"name": "nonce", "type": "function", "inputs": [], "outputs": [{"type": "uint256"}]}]
90
- safe_contract = self.chain_interface.web3._web3.eth.contract(
91
- address=Web3.to_checksum_address(multisig), abi=safe_abi
92
- )
93
- safe_nonce = safe_contract.functions.nonce().call()
94
- except Exception as e:
95
- # Fallback or log error? If safe read fails, something is very wrong.
96
- # But we don't want to crash the whole status check if possible.
97
- # For now, let's log and keep 0 or re-raise if critical.
98
- # safe_nonce is critical for liveness check (diffNonces).
99
- from loguru import logger
100
- logger.warning(f"Failed to read Safe nonce for {multisig}: {e}")
101
-
102
- # 2. Get Mech Requests Count
103
- mech_requests = 0
104
-
105
- if self.mech_marketplace:
106
- # Case A: Marketplace (MM)
107
- try:
108
- # Minimal ABI for Marketplace mapRequestCounts
109
- mp_abi = [{"name": "mapRequestCounts", "type": "function", "inputs": [{"type": "address"}], "outputs": [{"type": "uint256"}]}]
110
- mp_contract = self.chain_interface.web3._web3.eth.contract(
111
- address=Web3.to_checksum_address(self.mech_marketplace), abi=mp_abi
112
- )
113
- mech_requests = mp_contract.functions.mapRequestCounts(multisig).call()
114
- except Exception as e:
115
- from loguru import logger
116
- logger.warning(f"Failed to read Marketplace requests for {multisig}: {e}")
117
- else:
118
- # Case B: Legacy (AgentMech)
119
- try:
120
- # Minimal ABI for AgentMech getRequestsCount
121
- mech_abi = [{"name": "getRequestsCount", "type": "function", "inputs": [{"type": "address"}], "outputs": [{"type": "uint256"}]}]
122
- mech_contract = self.chain_interface.web3._web3.eth.contract(
123
- address=Web3.to_checksum_address(self.agent_mech), abi=mech_abi
124
- )
125
- mech_requests = mech_contract.functions.getRequestsCount(multisig).call()
126
- except Exception as e:
127
- from loguru import logger
128
- logger.warning(f"Failed to read AgentMech requests for {multisig}: {e}")
129
-
130
- return (safe_nonce, mech_requests)
80
+ nonces = self.contract.functions.getMultisigNonces(multisig).call()
81
+ return (nonces[0], nonces[1])
131
82
 
132
83
  def is_ratio_pass(
133
84
  self,
@@ -0,0 +1,68 @@
1
+ """Mech Marketplace V1 contract interaction (VERSION 1.0.0).
2
+
3
+ This contract version is used by older staking programs like Expert 17 MM.
4
+ It has a different request signature than v2, requiring staking instance
5
+ and service ID parameters for both the mech and the requester.
6
+
7
+ Key differences from v2:
8
+ - request() takes staking instance + service ID for both mech and requester
9
+ - No payment types or balance trackers
10
+ - No checkMech or mapPaymentTypeBalanceTrackers functions
11
+ """
12
+
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Dict, Optional
16
+
17
+ from iwa.core.contracts.contract import ContractInstance
18
+ from iwa.core.types import EthereumAddress
19
+
20
+
21
+ @dataclass
22
+ class V1RequestParams:
23
+ """Parameters for v1 marketplace request."""
24
+
25
+ data: bytes
26
+ priority_mech: str
27
+ priority_mech_staking_instance: str
28
+ priority_mech_service_id: int
29
+ requester_staking_instance: str
30
+ requester_service_id: int
31
+ response_timeout: int = 300
32
+ value: int = 10_000_000_000_000_000 # 0.01 xDAI
33
+
34
+
35
+ class MechMarketplaceV1Contract(ContractInstance):
36
+ """Class to interact with the Mech Marketplace v1 contract (VERSION 1.0.0).
37
+
38
+ This is the older marketplace used by staking contracts like Expert 17 MM.
39
+ """
40
+
41
+ name = "mech_marketplace_v1"
42
+ abi_path = Path(__file__).parent / "abis" / "mech_marketplace_v1.json"
43
+
44
+ def prepare_request_tx(
45
+ self,
46
+ from_address: EthereumAddress,
47
+ params: V1RequestParams,
48
+ ) -> Optional[Dict]:
49
+ """Prepare a v1 marketplace request transaction.
50
+
51
+ v1 ABI: request(bytes data, address priorityMech,
52
+ address priorityMechStakingInstance, uint256 priorityMechServiceId,
53
+ address requesterStakingInstance, uint256 requesterServiceId,
54
+ uint256 responseTimeout)
55
+ """
56
+ return self.prepare_transaction(
57
+ method_name="request",
58
+ method_kwargs={
59
+ "data": params.data,
60
+ "priorityMech": params.priority_mech,
61
+ "priorityMechStakingInstance": params.priority_mech_staking_instance,
62
+ "priorityMechServiceId": params.priority_mech_service_id,
63
+ "requesterStakingInstance": params.requester_staking_instance,
64
+ "requesterServiceId": params.requester_service_id,
65
+ "responseTimeout": params.response_timeout,
66
+ },
67
+ tx_params={"from": from_address, "value": params.value},
68
+ )
@@ -173,7 +173,7 @@ def main(): # noqa: C901
173
173
  # Get contract addresses
174
174
  protocol_contracts = OLAS_CONTRACTS.get("gnosis", {})
175
175
  legacy_mech_address = protocol_contracts.get("OLAS_MECH")
176
- marketplace_address = protocol_contracts.get("OLAS_MECH_MARKETPLACE")
176
+ marketplace_address = protocol_contracts.get("OLAS_MECH_MARKETPLACE_V2")
177
177
 
178
178
  # Step 3: Send Legacy Mech Request
179
179
  print_step("Step 3: Send Legacy Mech Request", "3️⃣")
@@ -1,28 +1,57 @@
1
1
  """Mech manager mixin.
2
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.
3
+ This module handles sending mech requests for OLAS services. There are THREE
4
+ distinct flows for mech requests, and the correct one is automatically selected
5
+ based on the service's staking contract configuration:
6
+
7
+ Flow Selection Logic:
8
+ 1. `get_marketplace_config()` checks if staking contract's activity checker
9
+ has a non-zero `mechMarketplace` address
10
+ 2. If yes marketplace request (v1 or v2 depending on address)
11
+ 3. If no → legacy mech request
12
+
13
+ ┌─────────────────────────────────────────────────────────────────────────────┐
14
+ FLOW 1: Legacy Mech (use_marketplace=False)
15
+ ├─────────────────────────────────────────────────────────────────────────────┤
16
+ Contract: Legacy Mech (0x77af31De...) │
17
+ │ Used by: NON-MM staking contracts (e.g., "Expert X (Yk OLAS)") │
18
+ │ Counting: agentMech.getRequestsCount(multisig) │
19
+ Method: _send_legacy_mech_request() │
20
+ └─────────────────────────────────────────────────────────────────────────────┘
21
+
22
+ ┌─────────────────────────────────────────────────────────────────────────────┐
23
+ FLOW 2: Marketplace v2 (use_marketplace=True, marketplace=0x735F...) │
24
+ ├─────────────────────────────────────────────────────────────────────────────┤
25
+ Contract: MechMarketplace v2 (0x735FAAb1c...) │
26
+ │ Used by: Newer MM staking contracts │
27
+ │ Counting: mechMarketplace.mapRequestCounts(multisig) │
28
+ │ Method: _send_marketplace_mech_request() → MechMarketplaceContract │
29
+ │ Signature: request(bytes,uint256,bytes32,address,uint256,bytes) │
30
+ │ Note: Uses payment types (PAYMENT_TYPE_NATIVE) │
31
+ └─────────────────────────────────────────────────────────────────────────────┘
32
+
33
+ ┌─────────────────────────────────────────────────────────────────────────────┐
34
+ │ FLOW 3: Marketplace v1 (use_marketplace=True, marketplace=0x4554...) │
35
+ ├─────────────────────────────────────────────────────────────────────────────┤
36
+ │ Contract: MechMarketplace v1 (0x4554fE75...) [VERSION 1.0.0] │
37
+ │ Used by: Older MM contracts (e.g., "Expert 17 MM", trader_ant) │
38
+ │ Counting: mechMarketplace.mapRequestCounts(multisig) │
39
+ │ Method: _send_v1_marketplace_request() → MechMarketplaceV1Contract │
40
+ │ Signature: request(bytes,address,address,uint256,address,uint256,uint256) │
41
+ │ Note: Requires staking instance + service ID for mech AND requester │
42
+ │ No payment types - simpler but different parameter set │
43
+ └─────────────────────────────────────────────────────────────────────────────┘
44
+
45
+ Important:
46
+ If a service is staked in an MM contract but sends requests to the wrong
47
+ marketplace (or uses legacy flow), those requests will NOT be counted by
48
+ the activity checker, and the service will not receive staking rewards.
49
+
50
+ The dispatch logic:
51
+ 1. _send_marketplace_mech_request() checks if marketplace ∈ V1_MARKETPLACES
52
+ 2. If v1 → dispatches to _send_v1_marketplace_request()
53
+ 3. If v2 → continues with MechMarketplaceContract (v2 ABI)
54
+
26
55
  """
27
56
 
28
57
  from typing import Optional
@@ -37,6 +66,31 @@ from iwa.plugins.olas.constants import (
37
66
  )
38
67
  from iwa.plugins.olas.contracts.mech import MechContract
39
68
  from iwa.plugins.olas.contracts.mech_marketplace import MechMarketplaceContract
69
+ from iwa.plugins.olas.contracts.mech_marketplace_v1 import (
70
+ MechMarketplaceV1Contract,
71
+ V1RequestParams,
72
+ )
73
+
74
+ # Maps marketplace address to (priority_mech_address, priority_mech_service_id, mech_staking_instance)
75
+ # Source: olas-operate-middleware profiles.py and manage.py
76
+ # The 3rd element (staking instance) is only needed for v1 marketplaces
77
+ DEFAULT_PRIORITY_MECH = {
78
+ "0x4554fE75c1f5576c1d7F765B2A036c199Adae329": (
79
+ "0x552cEA7Bc33CbBEb9f1D90c1D11D2C6daefFd053",
80
+ 975,
81
+ "0x998dEFafD094817EF329f6dc79c703f1CF18bC90", # Mech staking instance for v1
82
+ ),
83
+ "0x735FAAb1c4Ec41128c367AFb5c3baC73509f70bB": (
84
+ "0xC05e7412439bD7e91730a6880E18d5D5873F632C",
85
+ 2182,
86
+ None, # v2 doesn't need staking instance
87
+ ),
88
+ }
89
+
90
+ # Marketplace v1 addresses (use different request signature)
91
+ V1_MARKETPLACES = {
92
+ "0x4554fE75c1f5576c1d7F765B2A036c199Adae329", # VERSION 1.0.0
93
+ }
40
94
 
41
95
 
42
96
  class MechManagerMixin:
@@ -72,17 +126,26 @@ class MechManagerMixin:
72
126
  checker = staking.activity_checker
73
127
 
74
128
  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")
129
+ # Get priority mech from mapping based on marketplace address
130
+ marketplace_addr = Web3.to_checksum_address(checker.mech_marketplace)
131
+ priority_mech_info = DEFAULT_PRIORITY_MECH.get(marketplace_addr)
132
+
133
+ if priority_mech_info:
134
+ priority_mech = priority_mech_info[0] # First element is mech address
135
+ else:
136
+ # Fallback to constants if marketplace not in mapping
137
+ protocol_contracts = OLAS_CONTRACTS.get(self.chain_name, {})
138
+ priority_mech = protocol_contracts.get("OLAS_MECH_MARKETPLACE_PRIORITY")
139
+ logger.warning(
140
+ f"[MECH] Marketplace {marketplace_addr} not in DEFAULT_PRIORITY_MECH, "
141
+ f"using fallback priority_mech: {priority_mech}"
142
+ )
80
143
 
81
144
  logger.info(
82
145
  f"[MECH] Service {self.service.service_id} requires marketplace requests "
83
- f"(marketplace={checker.mech_marketplace}, priority_mech={priority_mech})"
146
+ f"(marketplace={marketplace_addr}, priority_mech={priority_mech})"
84
147
  )
85
- return (True, checker.mech_marketplace, priority_mech)
148
+ return (True, marketplace_addr, priority_mech)
86
149
 
87
150
  return (False, None, None)
88
151
 
@@ -134,17 +197,22 @@ class MechManagerMixin:
134
197
  return None
135
198
 
136
199
  # Auto-detect marketplace requirement if not explicitly specified
200
+ detected_marketplace = None
137
201
  if use_marketplace is None:
138
202
  use_marketplace, detected_marketplace, detected_priority_mech = (
139
203
  self.get_marketplace_config()
140
204
  )
141
205
  if use_marketplace:
142
206
  priority_mech = priority_mech or detected_priority_mech
207
+ mech_address = mech_address or detected_marketplace
143
208
 
144
209
  if use_marketplace:
210
+ # Use detected marketplace if available, otherwise _send_marketplace_mech_request
211
+ # will fall back to constant
145
212
  return self._send_marketplace_mech_request(
146
213
  data=data,
147
214
  value=value,
215
+ marketplace_address=detected_marketplace,
148
216
  priority_mech=priority_mech,
149
217
  max_delivery_rate=max_delivery_rate,
150
218
  payment_type=payment_type,
@@ -204,7 +272,12 @@ class MechManagerMixin:
204
272
  )
205
273
 
206
274
  def _validate_priority_mech(self, marketplace, priority_mech: str) -> bool:
207
- """Validate priority mech is registered on marketplace."""
275
+ """Validate priority mech is registered on marketplace.
276
+
277
+ Note: OLD marketplace v1 (0x4554...) doesn't have checkMech function.
278
+ In that case, we skip validation and proceed - v1 doesn't require
279
+ mech registration.
280
+ """
208
281
  try:
209
282
  mech_multisig = marketplace.call("checkMech", priority_mech)
210
283
  if mech_multisig == ZERO_ADDRESS:
@@ -212,8 +285,19 @@ class MechManagerMixin:
212
285
  return False
213
286
  logger.debug(f"Priority mech {priority_mech} -> multisig {mech_multisig}")
214
287
  except Exception as e:
215
- logger.error(f"Failed to verify priority mech registration: {e}")
216
- return False
288
+ # Check if this is a revert (v1 doesn't have checkMech) vs a network error
289
+ error_str = str(e).lower()
290
+ if "reverted" in error_str or "execution reverted" in error_str:
291
+ # v1 marketplaces don't have checkMech - skip validation
292
+ logger.warning(
293
+ f"Could not validate priority mech (marketplace may be v1): {e}. "
294
+ "Proceeding without validation."
295
+ )
296
+ return True
297
+ else:
298
+ # Real error (network, timeout, etc.) - fail validation
299
+ logger.error(f"Failed to validate priority mech (network error?): {e}")
300
+ return False
217
301
 
218
302
  # Log mech factory info (optional validation)
219
303
  try:
@@ -232,7 +316,11 @@ class MechManagerMixin:
232
316
  def _validate_marketplace_params(
233
317
  self, marketplace, response_timeout: int, payment_type: bytes
234
318
  ) -> bool:
235
- """Validate marketplace parameters."""
319
+ """Validate marketplace parameters.
320
+
321
+ Note: v1 marketplaces may not have all validation functions.
322
+ We proceed with warnings when validation functions are unavailable.
323
+ """
236
324
  # Validate response_timeout bounds
237
325
  try:
238
326
  min_timeout = marketplace.call("minResponseTimeout")
@@ -248,70 +336,129 @@ class MechManagerMixin:
248
336
  except Exception as e:
249
337
  logger.warning(f"Could not validate response_timeout bounds: {e}")
250
338
 
251
- # Validate payment type has balance tracker
339
+ # Validate payment type has balance tracker (v2 only)
252
340
  try:
253
341
  balance_tracker = marketplace.call("mapPaymentTypeBalanceTrackers", payment_type)
254
342
  if balance_tracker == ZERO_ADDRESS:
255
- logger.error(f"No balance tracker for payment type 0x{payment_type.hex()}")
343
+ # This is a validation failure for v2 - return False
344
+ logger.error(
345
+ f"No balance tracker for payment type 0x{payment_type.hex()}. "
346
+ "This is required for v2 marketplace requests."
347
+ )
256
348
  return False
257
- logger.debug(f"Payment type balance tracker: {balance_tracker}")
349
+ else:
350
+ logger.debug(f"Payment type balance tracker: {balance_tracker}")
258
351
  except Exception as e:
259
- logger.warning(f"Could not validate payment type: {e}")
352
+ # Check if this is a revert (v1 doesn't have this function) vs a network error
353
+ error_str = str(e).lower()
354
+ if "reverted" in error_str or "execution reverted" in error_str:
355
+ # v1 marketplaces don't have mapPaymentTypeBalanceTrackers - skip
356
+ logger.warning(
357
+ f"Could not validate payment type (marketplace may be v1): {e}. "
358
+ "Proceeding without validation."
359
+ )
360
+ else:
361
+ # Real error - fail validation
362
+ logger.error(f"Failed to validate payment type (network error?): {e}")
363
+ return False
260
364
 
261
365
  return True
262
366
 
367
+ def _resolve_marketplace_config(
368
+ self, marketplace_addr: Optional[str], priority_addr: Optional[str]
369
+ ) -> tuple[str, str]:
370
+ """Resolve marketplace and priority mech addresses. Returns (marketplace, priority)."""
371
+ chain_name = self.chain_name if self.service else getattr(self, "chain_name", "gnosis")
372
+ protocol_contracts = OLAS_CONTRACTS.get(chain_name, {})
373
+
374
+ resolved_mp = marketplace_addr or protocol_contracts.get("OLAS_MECH_MARKETPLACE_V2")
375
+ if not resolved_mp:
376
+ raise ValueError(f"Mech Marketplace address not found for chain {chain_name}")
377
+
378
+ if not priority_addr:
379
+ raise ValueError("priority_mech is required for marketplace requests")
380
+
381
+ return str(resolved_mp), Web3.to_checksum_address(priority_addr)
382
+
383
+ def _prepare_marketplace_params(
384
+ self,
385
+ value: Optional[int],
386
+ max_delivery_rate: Optional[int],
387
+ payment_type: Optional[bytes],
388
+ ) -> tuple[int, int, bytes]:
389
+ """Prepare default values for marketplace parameters."""
390
+ p_type = payment_type or bytes.fromhex(PAYMENT_TYPE_NATIVE)
391
+ val = value if value is not None else 10_000_000_000_000_000
392
+ rate = max_delivery_rate if max_delivery_rate is not None else val
393
+ return val, rate, p_type
394
+
263
395
  def _send_marketplace_mech_request(
264
396
  self,
265
397
  data: bytes,
266
398
  value: Optional[int] = None,
267
399
  priority_mech: Optional[str] = None,
400
+ marketplace_address: Optional[str] = None,
268
401
  max_delivery_rate: Optional[int] = None,
269
402
  payment_type: Optional[bytes] = None,
270
403
  payment_data: bytes = b"",
271
404
  response_timeout: int = 300,
272
405
  ) -> Optional[str]:
273
- """Send a marketplace mech request with validation."""
406
+ """Send a marketplace mech request with validation.
407
+
408
+ Args:
409
+ data: Request data payload (bytes).
410
+ value: Native currency value to send with request (wei).
411
+ priority_mech: Priority mech address for request processing.
412
+ marketplace_address: The marketplace contract address from activity checker.
413
+ If None, falls back to OLAS_MECH_MARKETPLACE_V2 constant.
414
+ max_delivery_rate: Maximum delivery rate for the mech.
415
+ payment_type: Payment type bytes32 (defaults to PAYMENT_TYPE_NATIVE).
416
+ payment_data: Additional payment data.
417
+ response_timeout: Timeout for response in seconds.
418
+
419
+ Returns:
420
+ Transaction hash if successful, None otherwise.
421
+
422
+ """
274
423
  if not self.service:
275
424
  logger.error("No active service")
276
425
  return None
277
426
 
278
- multisig_address = self.service.multisig_address
279
- chain_name = self.chain_name if self.service else getattr(self, "chain_name", "gnosis")
280
- protocol_contracts = OLAS_CONTRACTS.get(chain_name, {})
281
- marketplace_address = protocol_contracts.get("OLAS_MECH_MARKETPLACE")
282
-
283
- if not marketplace_address:
284
- logger.error(f"Mech Marketplace address not found for chain {chain_name}")
427
+ try:
428
+ marketplace_address, priority_mech = self._resolve_marketplace_config(
429
+ marketplace_address, priority_mech
430
+ )
431
+ except ValueError as e:
432
+ logger.error(e)
285
433
  return None
286
434
 
287
- if not priority_mech:
288
- logger.error("priority_mech is required for marketplace requests")
289
- return None
435
+ # Dispatch to v1 handler if marketplace is v1
436
+ if marketplace_address in V1_MARKETPLACES:
437
+ return self._send_v1_marketplace_request(
438
+ data=data,
439
+ marketplace_address=marketplace_address,
440
+ priority_mech=priority_mech,
441
+ response_timeout=response_timeout,
442
+ value=value,
443
+ )
290
444
 
291
- priority_mech = Web3.to_checksum_address(priority_mech)
292
- marketplace = MechMarketplaceContract(str(marketplace_address), chain_name=chain_name)
445
+ # v2 flow
446
+ marketplace = MechMarketplaceContract(marketplace_address, chain_name=self.chain_name)
293
447
 
294
448
  if not self._validate_priority_mech(marketplace, priority_mech):
295
449
  return None
296
450
 
297
- # Set defaults for payment
298
- if payment_type is None:
299
- payment_type = bytes.fromhex(PAYMENT_TYPE_NATIVE)
300
-
301
- if value is None:
302
- value = 10_000_000_000_000_000
303
- logger.info(f"Using default value: {value} wei (0.01 xDAI)")
304
-
305
- if max_delivery_rate is None:
306
- max_delivery_rate = value
307
- logger.info(f"Using value as max_delivery_rate: {max_delivery_rate}")
451
+ # Set defaults for payment and delivery
452
+ value, max_delivery_rate, payment_type = self._prepare_marketplace_params(
453
+ value, max_delivery_rate, payment_type
454
+ )
308
455
 
309
456
  if not self._validate_marketplace_params(marketplace, response_timeout, payment_type):
310
457
  return None
311
458
 
312
459
  # Prepare transaction
313
460
  tx_data = marketplace.prepare_request_tx(
314
- from_address=multisig_address,
461
+ from_address=self.service.multisig_address,
315
462
  request_data=data,
316
463
  priority_mech=priority_mech,
317
464
  response_timeout=response_timeout,
@@ -332,6 +479,77 @@ class MechManagerMixin:
332
479
  expected_event="MarketplaceRequest",
333
480
  )
334
481
 
482
+ def _send_v1_marketplace_request(
483
+ self,
484
+ data: bytes,
485
+ marketplace_address: str,
486
+ priority_mech: str,
487
+ response_timeout: int = 300,
488
+ value: Optional[int] = None,
489
+ ) -> Optional[str]:
490
+ """Send a v1 marketplace mech request.
491
+
492
+ v1 marketplace (VERSION 1.0.0) requires staking instance and service ID
493
+ for both the mech and the requester, unlike v2 which uses payment types.
494
+ """
495
+ if not self.service:
496
+ logger.error("No active service")
497
+ return None
498
+
499
+ # Get mech info from DEFAULT_PRIORITY_MECH (now a 3-tuple)
500
+ mech_info = DEFAULT_PRIORITY_MECH.get(marketplace_address)
501
+ if not mech_info or len(mech_info) < 3:
502
+ logger.error(f"No priority mech info for v1 marketplace {marketplace_address}")
503
+ return None
504
+
505
+ priority_mech_address, priority_mech_service_id, priority_mech_staking = mech_info
506
+
507
+ if not priority_mech_staking:
508
+ logger.error(f"No mech staking instance for v1 marketplace {marketplace_address}")
509
+ return None
510
+
511
+ # Get requester staking info from current service
512
+ requester_staking_instance = self.service.staking_contract_address
513
+ requester_service_id = self.service.service_id
514
+
515
+ if not requester_staking_instance:
516
+ logger.error("No staking contract for current service (required for v1)")
517
+ return None
518
+
519
+ # Build v1 request params
520
+ params = V1RequestParams(
521
+ data=data,
522
+ priority_mech=priority_mech_address,
523
+ priority_mech_staking_instance=priority_mech_staking,
524
+ priority_mech_service_id=priority_mech_service_id,
525
+ requester_staking_instance=requester_staking_instance,
526
+ requester_service_id=requester_service_id,
527
+ response_timeout=response_timeout,
528
+ value=value or 10_000_000_000_000_000, # 0.01 xDAI default
529
+ )
530
+
531
+ logger.info(
532
+ f"[MECH-V1] Sending v1 marketplace request to {marketplace_address} "
533
+ f"(mech={priority_mech_address}, mech_svc={priority_mech_service_id})"
534
+ )
535
+
536
+ marketplace = MechMarketplaceV1Contract(marketplace_address, chain_name=self.chain_name)
537
+ tx_data = marketplace.prepare_request_tx(
538
+ from_address=self.service.multisig_address,
539
+ params=params,
540
+ )
541
+
542
+ if not tx_data:
543
+ logger.error("Failed to prepare v1 marketplace request transaction")
544
+ return None
545
+
546
+ return self._execute_mech_tx(
547
+ tx_data=tx_data,
548
+ to_address=str(marketplace_address),
549
+ contract_instance=marketplace,
550
+ expected_event="MarketplaceRequest",
551
+ )
552
+
335
553
  def _execute_mech_tx(
336
554
  self,
337
555
  tx_data: dict,
@@ -343,8 +343,8 @@ if __name__ == "__main__": # pragma: no cover
343
343
  "-p",
344
344
  type=int,
345
345
  default=1,
346
- choices=[1, 2, 3],
347
- help="Tenderly profile to use (1 or 2)",
346
+ choices=[1, 2, 3, 4],
347
+ help="Tenderly profile to use (1-4)",
348
348
  )
349
349
  args = parser.parse_args()
350
350
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iwa
3
- Version: 0.0.17
3
+ Version: 0.0.19
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