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.
- iwa/core/chain/interface.py +1 -65
- iwa/core/pricing.py +1 -4
- iwa/core/services/transaction.py +72 -70
- iwa/core/services/transfer/erc20.py +8 -0
- iwa/core/services/transfer/native.py +47 -13
- iwa/plugins/olas/constants.py +41 -39
- iwa/plugins/olas/contracts/abis/mech_marketplace_v1.json +828 -0
- iwa/plugins/olas/contracts/activity_checker.py +2 -51
- iwa/plugins/olas/contracts/mech_marketplace_v1.py +68 -0
- iwa/plugins/olas/scripts/test_full_mech_flow.py +1 -1
- iwa/plugins/olas/service_manager/mech.py +281 -63
- iwa/tools/reset_tenderly.py +2 -2
- {iwa-0.0.17.dist-info → iwa-0.0.19.dist-info}/METADATA +1 -1
- {iwa-0.0.17.dist-info → iwa-0.0.19.dist-info}/RECORD +23 -21
- tests/test_chain.py +2 -97
- tests/test_legacy_wallet.py +6 -6
- tests/test_service_transaction.py +23 -8
- tests/test_staking_router.py +14 -6
- tests/test_transaction_service.py +20 -6
- {iwa-0.0.17.dist-info → iwa-0.0.19.dist-info}/WHEEL +0 -0
- {iwa-0.0.17.dist-info → iwa-0.0.19.dist-info}/entry_points.txt +0 -0
- {iwa-0.0.17.dist-info → iwa-0.0.19.dist-info}/licenses/LICENSE +0 -0
- {iwa-0.0.17.dist-info → iwa-0.0.19.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
86
|
-
|
|
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("
|
|
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
|
|
4
|
-
distinct flows for mech requests, and the correct one
|
|
5
|
-
the service's staking contract:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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={
|
|
146
|
+
f"(marketplace={marketplace_addr}, priority_mech={priority_mech})"
|
|
84
147
|
)
|
|
85
|
-
return (True,
|
|
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
|
-
|
|
216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
349
|
+
else:
|
|
350
|
+
logger.debug(f"Payment type balance tracker: {balance_tracker}")
|
|
258
351
|
except Exception as e:
|
|
259
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
288
|
-
|
|
289
|
-
return
|
|
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
|
-
|
|
292
|
-
marketplace = MechMarketplaceContract(
|
|
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
|
-
|
|
299
|
-
|
|
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,
|
iwa/tools/reset_tenderly.py
CHANGED
|
@@ -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
|
|
346
|
+
choices=[1, 2, 3, 4],
|
|
347
|
+
help="Tenderly profile to use (1-4)",
|
|
348
348
|
)
|
|
349
349
|
args = parser.parse_args()
|
|
350
350
|
|