mech-client 0.12.1__tar.gz → 0.14.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. {mech_client-0.12.1 → mech_client-0.14.0}/PKG-INFO +1 -1
  2. mech_client-0.14.0/mech_client/__init__.py +3 -0
  3. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/cli.py +0 -5
  4. mech_client-0.14.0/mech_client/delivery.py +156 -0
  5. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/interact.py +8 -5
  6. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/marketplace_interact.py +129 -120
  7. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/wss.py +25 -74
  8. {mech_client-0.12.1 → mech_client-0.14.0}/pyproject.toml +1 -1
  9. mech_client-0.12.1/mech_client/__init__.py +0 -3
  10. {mech_client-0.12.1 → mech_client-0.14.0}/LICENSE +0 -0
  11. {mech_client-0.12.1 → mech_client-0.14.0}/README.md +0 -0
  12. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/AgentMech.json +0 -0
  13. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/AgentRegistry.json +0 -0
  14. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/AgreementStoreManager.base.json +0 -0
  15. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/AgreementStoreManager.gnosis.json +0 -0
  16. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/BalanceTrackerFixedPriceNative.json +0 -0
  17. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/BalanceTrackerFixedPriceToken.json +0 -0
  18. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/BalanceTrackerNvmSubscriptionNative.json +0 -0
  19. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/BalanceTrackerNvmSubscriptionToken.json +0 -0
  20. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/ComplementaryServiceMetadata.json +0 -0
  21. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/DIDRegistry.base.json +0 -0
  22. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/DIDRegistry.gnosis.json +0 -0
  23. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/EscrowPaymentCondition.base.json +0 -0
  24. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/EscrowPaymentCondition.gnosis.json +0 -0
  25. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/IERC1155.json +0 -0
  26. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/IMech.json +0 -0
  27. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/IToken.json +0 -0
  28. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/LockPaymentCondition.base.json +0 -0
  29. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/LockPaymentCondition.gnosis.json +0 -0
  30. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/MechMarketplace.json +0 -0
  31. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/NFTSalesTemplate.base.json +0 -0
  32. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/NFTSalesTemplate.gnosis.json +0 -0
  33. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/NeverminedConfig.base.json +0 -0
  34. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/NeverminedConfig.gnosis.json +0 -0
  35. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/SubscriptionNFT.base.json +0 -0
  36. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/SubscriptionNFT.gnosis.json +0 -0
  37. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/SubscriptionProvider.base.json +0 -0
  38. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/SubscriptionProvider.gnosis.json +0 -0
  39. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/SubscriptionToken.base.json +0 -0
  40. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/TransferNFTCondition.base.json +0 -0
  41. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/abis/TransferNFTCondition.gnosis.json +0 -0
  42. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/acn.py +0 -0
  43. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/configs/mechs.json +0 -0
  44. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/fetch_ipfs_hash.py +0 -0
  45. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/__init__.py +0 -0
  46. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/README.md +0 -0
  47. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/__init__.py +0 -0
  48. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/acn.proto +0 -0
  49. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/acn_pb2.py +0 -0
  50. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/custom_types.py +0 -0
  51. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/dialogues.py +0 -0
  52. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/message.py +0 -0
  53. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/protocol.yaml +0 -0
  54. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/serialization.py +0 -0
  55. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/tests/__init__.py +0 -0
  56. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/tests/test_acn.py +0 -0
  57. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/tests/test_acn_dialogues.py +0 -0
  58. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn/tests/test_acn_messages.py +0 -0
  59. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn_data_share/README.md +0 -0
  60. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn_data_share/__init__.py +0 -0
  61. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn_data_share/acn_data_share.proto +0 -0
  62. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn_data_share/acn_data_share_pb2.py +0 -0
  63. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn_data_share/dialogues.py +0 -0
  64. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn_data_share/message.py +0 -0
  65. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn_data_share/protocol.yaml +0 -0
  66. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn_data_share/serialization.py +0 -0
  67. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn_data_share/tests/test_acn_data_share_dialogues.py +0 -0
  68. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/acn_data_share/tests/test_acn_data_share_messages.py +0 -0
  69. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/p2p_libp2p_client/README.md +0 -0
  70. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/p2p_libp2p_client/__init__.py +0 -0
  71. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/p2p_libp2p_client/connection.py +0 -0
  72. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/helpers/p2p_libp2p_client/connection.yaml +0 -0
  73. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/mech_marketplace_subgraph.py +0 -0
  74. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/mech_marketplace_tool_management.py +0 -0
  75. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/mech_tool_management.py +0 -0
  76. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/prompt_to_ipfs.py +0 -0
  77. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/push_to_ipfs.py +0 -0
  78. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/subgraph.py +0 -0
  79. {mech_client-0.12.1 → mech_client-0.14.0}/mech_client/to_png.py +0 -0
  80. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/__init__.py +0 -0
  81. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/benchmark.sh +0 -0
  82. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/bump.py +0 -0
  83. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/deposit_native.py +0 -0
  84. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/deposit_token.py +0 -0
  85. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscribe.py +0 -0
  86. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/contracts/agreement_manager.py +0 -0
  87. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/contracts/base_contract.py +0 -0
  88. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/contracts/did_registry.py +0 -0
  89. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/contracts/escrow_payment.py +0 -0
  90. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/contracts/lock_payment.py +0 -0
  91. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/contracts/nft.py +0 -0
  92. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/contracts/nft_sales.py +0 -0
  93. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/contracts/subscription_provider.py +0 -0
  94. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/contracts/token.py +0 -0
  95. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/contracts/transfer_nft.py +0 -0
  96. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/envs/base.env +0 -0
  97. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/envs/gnosis.env +0 -0
  98. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/manager.py +0 -0
  99. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/nvm_subscription/resources/networks.json +0 -0
  100. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/utils.py +0 -0
  101. {mech_client-0.12.1 → mech_client-0.14.0}/scripts/whitelist.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mech-client
3
- Version: 0.12.1
3
+ Version: 0.14.0
4
4
  Summary: Basic client to interact with a mech
5
5
  License: Apache-2.0
6
6
  Author: David Minarsch
@@ -0,0 +1,3 @@
1
+ """Mech client."""
2
+
3
+ __version__ = "0.14.0"
@@ -180,11 +180,6 @@ def interact( # pylint: disable=too-many-arguments,too-many-locals
180
180
  private_key_path=key,
181
181
  tools=tools,
182
182
  extra_attributes=extra_attributes_dict,
183
- confirmation_type=(
184
- ConfirmationType(confirm)
185
- if confirm is not None
186
- else ConfirmationType.WAIT_FOR_BOTH
187
- ),
188
183
  retries=retries,
189
184
  timeout=timeout,
190
185
  sleep=sleep,
@@ -0,0 +1,156 @@
1
+ # -*- coding: utf-8 -*-
2
+ # ------------------------------------------------------------------------------
3
+ #
4
+ # Copyright 2025 Valory AG
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # ------------------------------------------------------------------------------
19
+
20
+ """Onchain delivery helpers."""
21
+
22
+ import time
23
+ from typing import Any, Dict, List, Optional, Tuple
24
+
25
+ from aea_ledger_ethereum import EthereumApi
26
+ from eth_abi import decode
27
+ from web3.constants import ADDRESS_ZERO
28
+ from web3.contract import Contract as Web3Contract
29
+
30
+
31
+ WAIT_SLEEP = 3.0
32
+ DELIVERY_MECH_INDEX = 1
33
+ DEFAULT_TIMEOUT = 900.0 # 15mins
34
+ IPFS_URL_TEMPLATE = "https://gateway.autonolas.tech/ipfs/f01701220{}"
35
+
36
+
37
+ async def watch_for_marketplace_data( # pylint: disable=too-many-arguments, unused-argument, too-many-locals
38
+ request_ids: List[str],
39
+ marketplace_contract: Web3Contract,
40
+ timeout: Optional[float] = None,
41
+ ) -> Any:
42
+ """
43
+ Watches for data on-chain.
44
+
45
+ :param request_ids: The IDs of the request.
46
+ :type request_ids: List[str]
47
+ :param marketplace_contract: The marketplace contract instance.
48
+ :type marketplace_contract: Web3Contract
49
+ :param timeout: Timeout to wait for the onchain data
50
+ :type timeout: float
51
+ :return: The data received from on-chain.
52
+ :rtype: Any
53
+ """
54
+ request_ids_data: Dict = {}
55
+ start_time = time.time()
56
+ # either use the timeout supplied by user or the default timeout of 15mins
57
+ timeout = timeout or DEFAULT_TIMEOUT
58
+ while True:
59
+ for request_id in request_ids:
60
+ request_id_info = marketplace_contract.functions.mapRequestIdInfos(
61
+ bytes.fromhex(request_id)
62
+ ).call()
63
+
64
+ # return empty data which is handled in the main method
65
+ if len(request_id_info) <= DELIVERY_MECH_INDEX:
66
+ return request_ids_data
67
+
68
+ delivery_mech = request_id_info[DELIVERY_MECH_INDEX]
69
+ if not isinstance(delivery_mech, str) or not delivery_mech.startswith("0x"):
70
+ return request_id_info
71
+
72
+ if delivery_mech != ADDRESS_ZERO:
73
+ request_ids_data.update({request_id: delivery_mech})
74
+
75
+ time.sleep(WAIT_SLEEP)
76
+
77
+ elapsed_time = time.time() - start_time
78
+ if elapsed_time >= timeout:
79
+ print("Timeout reached. Breaking the loop and returning empty data.")
80
+ return request_ids_data
81
+
82
+ if len(request_ids_data) == len(request_ids):
83
+ return request_ids_data
84
+
85
+
86
+ async def watch_for_mech_data_url( # pylint: disable=too-many-arguments, unused-argument, too-many-locals
87
+ request_ids: List[str],
88
+ from_block: int,
89
+ mech_contract_address: str,
90
+ mech_deliver_signature: str,
91
+ ledger_api: EthereumApi,
92
+ timeout: Optional[float] = None,
93
+ ) -> Any:
94
+ """
95
+ Watches for data on-chain.
96
+
97
+ :param request_ids: The IDs of the request.
98
+ :type request_ids: List[str]
99
+ :param from_block: The from block to start searching logs.
100
+ :type from_block: int
101
+ :param mech_contract_address: The mech contract instance.
102
+ :type mech_contract_address: str
103
+ :param mech_deliver_signature: Topic signature for Deliver event
104
+ :type mech_deliver_signature: str
105
+ :param ledger_api: The Ethereum API used for interacting with the ledger.
106
+ :type ledger_api: EthereumApi
107
+ :param timeout: Timeout to wait for the onchain data
108
+ :type timeout: float
109
+ :return: The data received from on-chain.
110
+ :rtype: Any
111
+ """
112
+
113
+ results = {}
114
+ start_time = time.time()
115
+ # either use the timeout supplied by user or the default timeout of 15mins
116
+ timeout = timeout or DEFAULT_TIMEOUT
117
+
118
+ def get_logs(from_block_: int) -> List:
119
+ logs = ledger_api.api.eth.get_logs(
120
+ {
121
+ "fromBlock": from_block_,
122
+ "toBlock": "latest",
123
+ "address": mech_contract_address,
124
+ "topics": ["0x" + mech_deliver_signature],
125
+ }
126
+ )
127
+ return logs
128
+
129
+ def get_event_data(log: Dict) -> Tuple:
130
+ data_types = ["bytes32", "uint256", "bytes"]
131
+ data_bytes = bytes(log["data"])
132
+ request_id_bytes, _, delivery_data_bytes = decode(data_types, data_bytes)
133
+ return request_id_bytes, delivery_data_bytes
134
+
135
+ while True:
136
+ logs = get_logs(from_block)
137
+ latest_block = from_block
138
+ for log in logs:
139
+ latest_block = max(latest_block, log["blockNumber"])
140
+ event_data = get_event_data(log)
141
+ request_id, delivery_data = (data.hex() for data in event_data)
142
+ if request_id in results:
143
+ continue
144
+
145
+ if request_id in request_ids:
146
+ results[request_id] = IPFS_URL_TEMPLATE.format(delivery_data)
147
+
148
+ if len(results) == len(request_ids):
149
+ return results
150
+
151
+ from_block = latest_block + 1
152
+ time.sleep(WAIT_SLEEP)
153
+ elapsed_time = time.time() - start_time
154
+ if elapsed_time >= timeout:
155
+ print("Timeout reached. Breaking the loop and returning empty data.")
156
+ return results
@@ -200,7 +200,7 @@ def calculate_topic_id(event: Dict) -> str:
200
200
  return Web3.keccak(text=text).hex()
201
201
 
202
202
 
203
- def get_event_signatures(abi: List) -> Tuple[str, str]:
203
+ def get_mech_event_signatures(abi: List) -> Tuple[str, str]:
204
204
  """Calculate `Request` and `Deliver` event topics"""
205
205
  request, deliver = "", ""
206
206
  for obj in abi:
@@ -576,13 +576,16 @@ def interact( # pylint: disable=too-many-arguments,too-many-locals
576
576
  mech_contract = get_contract(
577
577
  contract_address=contract_address, abi=abi, ledger_api=ledger_api
578
578
  )
579
- request_event_signature, deliver_event_signature = get_event_signatures(abi=abi)
579
+ request_event_signature, deliver_event_signature = get_mech_event_signatures(
580
+ abi=abi
581
+ )
580
582
  register_event_handlers(
581
583
  wss=wss,
582
- contract_address=contract_address,
584
+ mech_contract_address=contract_address,
585
+ marketplace_contract_address=contract_address,
583
586
  crypto=crypto,
584
- request_signature=request_event_signature,
585
- deliver_signature=deliver_event_signature,
587
+ mech_request_signature=request_event_signature,
588
+ marketplace_deliver_signature=deliver_event_signature,
586
589
  )
587
590
  print("Sending Mech request...")
588
591
  price = mech_config.price or 10_000_000_000_000_000
@@ -24,40 +24,35 @@ import asyncio
24
24
  import json
25
25
  import sys
26
26
  import time
27
+ from collections import defaultdict
27
28
  from dataclasses import asdict, make_dataclass
28
29
  from datetime import datetime
29
30
  from enum import Enum
30
31
  from pathlib import Path
31
- from typing import Any, Dict, Optional, Tuple, cast
32
+ from typing import Any, Dict, List, Optional, Tuple, cast
32
33
 
33
34
  import requests
34
- import websocket
35
35
  from aea.crypto.base import Crypto
36
36
  from aea_ledger_ethereum import EthereumApi, EthereumCrypto
37
37
  from eth_utils import to_checksum_address
38
+ from web3._utils.events import event_abi_to_log_topic
38
39
  from web3.constants import ADDRESS_ZERO
39
40
  from web3.contract import Contract as Web3Contract
40
41
 
42
+ from mech_client.delivery import watch_for_marketplace_data, watch_for_mech_data_url
41
43
  from mech_client.fetch_ipfs_hash import fetch_ipfs_hash
42
44
  from mech_client.interact import (
43
- ConfirmationType,
44
45
  MAX_RETRIES,
45
46
  MechMarketplaceRequestConfig,
46
47
  PRIVATE_KEY_FILE_PATH,
47
48
  TIMEOUT,
48
49
  WAIT_SLEEP,
49
50
  get_contract,
50
- get_event_signatures,
51
51
  get_mech_config,
52
52
  )
53
53
  from mech_client.mech_marketplace_tool_management import get_mech_tools
54
54
  from mech_client.prompt_to_ipfs import push_metadata_to_ipfs
55
- from mech_client.wss import (
56
- register_event_handlers,
57
- wait_for_receipt,
58
- watch_for_marketplace_data_url_from_wss,
59
- watch_for_marketplace_request_ids,
60
- )
55
+ from mech_client.wss import wait_for_receipt, watch_for_marketplace_request_ids
61
56
 
62
57
 
63
58
  # false positives for [B105:hardcoded_password_string] Possible hardcoded password
@@ -122,11 +117,36 @@ CHAIN_TO_DEFAULT_MECH_MARKETPLACE_REQUEST_CONFIG = {
122
117
  }
123
118
 
124
119
 
120
+ def fetch_mech_deliver_event_signature(
121
+ ledger_api: EthereumApi,
122
+ priority_mech_address: str,
123
+ ) -> str:
124
+ """
125
+ Fetchs the mech's deliver event signature.
126
+
127
+ :param ledger_api: The Ethereum API used for interacting with the ledger.
128
+ :type ledger_api: EthereumApi
129
+ :param priority_mech_address: Requested mech address
130
+ :type priority_mech_address: str
131
+ :return: The event signature.
132
+ :rtype: str
133
+ """
134
+ with open(IMECH_ABI_PATH, encoding="utf-8") as f:
135
+ abi = json.load(f)
136
+
137
+ mech_contract = get_contract(
138
+ contract_address=priority_mech_address, abi=abi, ledger_api=ledger_api
139
+ )
140
+ deliver_event_abi = mech_contract.events.Deliver().abi
141
+ mech_deliver_event_signature = event_abi_to_log_topic(deliver_event_abi)
142
+ return mech_deliver_event_signature.hex()
143
+
144
+
125
145
  def fetch_mech_info(
126
146
  ledger_api: EthereumApi,
127
147
  mech_marketplace_contract: Web3Contract,
128
148
  priority_mech_address: str,
129
- ) -> Tuple[str, int, int, str, Web3Contract]:
149
+ ) -> Tuple[str, int, int, str]:
130
150
  """
131
151
  Fetchs the info of the requested mech.
132
152
 
@@ -136,8 +156,8 @@ def fetch_mech_info(
136
156
  :type mech_marketplace_contract: Web3Contract
137
157
  :param priority_mech_address: Requested mech address
138
158
  :type priority_mech_address: str
139
- :return: The mech info containing payment_type, service_id, max_delivery_rate, mech_payment_balance_tracker and Mech contract.
140
- :rtype: Tuple[str, int, int, str, Contract]
159
+ :return: The mech info containing payment_type, service_id, max_delivery_rate and mech_payment_balance_tracker.
160
+ :rtype: Tuple[str, int, int, str]
141
161
  """
142
162
 
143
163
  with open(IMECH_ABI_PATH, encoding="utf-8") as f:
@@ -166,7 +186,6 @@ def fetch_mech_info(
166
186
  service_id,
167
187
  max_delivery_rate,
168
188
  mech_payment_balance_tracker,
169
- mech_contract,
170
189
  )
171
190
 
172
191
 
@@ -495,80 +514,80 @@ def send_offchain_marketplace_request( # pylint: disable=too-many-arguments,too
495
514
 
496
515
 
497
516
  def wait_for_marketplace_data_url( # pylint: disable=too-many-arguments, unused-argument
498
- request_id: str,
499
- wss: websocket.WebSocket,
500
- mech_contract: Web3Contract,
501
- subgraph_url: str,
517
+ request_ids: List[str],
518
+ from_block: int,
519
+ marketplace_contract: Web3Contract,
502
520
  deliver_signature: str,
503
521
  ledger_api: EthereumApi,
504
- crypto: Crypto,
505
- confirmation_type: ConfirmationType = ConfirmationType.WAIT_FOR_BOTH,
522
+ timeout: Optional[float] = None,
506
523
  ) -> Any:
507
524
  """
508
525
  Wait for data from on-chain/off-chain.
509
526
 
510
- :param request_id: The ID of the request.
511
- :type request_id: str
512
- :param wss: The WebSocket connection object.
513
- :type wss: websocket.WebSocket
514
- :param mech_contract: The mech contract instance.
515
- :type mech_contract: Web3Contract
516
- :param subgraph_url: Subgraph URL.
517
- :type subgraph_url: str
518
- :param deliver_signature: Topic signature for MarketplaceDeliver event
527
+ :param request_ids: The IDs of the request.
528
+ :type request_ids: List[str]
529
+ :param from_block: The from block to start searching logs.
530
+ :type from_block: int
531
+ :param marketplace_contract: The mech contract instance.
532
+ :type marketplace_contract: Web3Contract
533
+ :param deliver_signature: Topic signature for Deliver event
519
534
  :type deliver_signature: str
520
535
  :param ledger_api: The Ethereum API used for interacting with the ledger.
521
536
  :type ledger_api: EthereumApi
522
- :param crypto: The cryptographic object.
523
- :type crypto: Crypto
524
- :param confirmation_type: The confirmation type for the interaction (default: ConfirmationType.WAIT_FOR_BOTH).
525
- :type confirmation_type: ConfirmationType
526
- :return: The data received from on-chain/off-chain.
537
+ :param timeout: Timeout to wait for the onchain data
538
+ :type timeout: float
539
+ :return: The data received from on-chain.
527
540
  :rtype: Any
528
541
  """
529
542
  loop = asyncio.new_event_loop()
530
543
  asyncio.set_event_loop(loop)
531
- tasks = []
532
-
533
- if confirmation_type in (
534
- ConfirmationType.OFF_CHAIN,
535
- ConfirmationType.WAIT_FOR_BOTH,
536
- ):
537
- print("Off chain to be implemented")
538
-
539
- if confirmation_type in (
540
- ConfirmationType.ON_CHAIN,
541
- ConfirmationType.WAIT_FOR_BOTH,
542
- ):
543
- on_chain_task = loop.create_task(
544
- watch_for_marketplace_data_url_from_wss(
545
- request_id=request_id,
546
- wss=wss,
547
- mech_contract=mech_contract,
548
- deliver_signature=deliver_signature,
544
+
545
+ async def _wait_for_marketplace_delivery_event() -> Any: # type: ignore
546
+ data = await watch_for_marketplace_data(
547
+ request_ids=request_ids,
548
+ marketplace_contract=marketplace_contract,
549
+ timeout=timeout,
550
+ )
551
+ return data
552
+
553
+ async def _wait_for_mech_data(future) -> Any: # type: ignore
554
+ marketplace_data_result = await future
555
+ requests_by_delivery_mech = defaultdict(list)
556
+ results: Dict = {}
557
+
558
+ # return with empty data is result is unexpected
559
+ if len(marketplace_data_result) == 0:
560
+ return results
561
+
562
+ for request_id, delivery_mech in marketplace_data_result.items():
563
+ requests_by_delivery_mech[delivery_mech].append(request_id)
564
+
565
+ for delivery_mech, request_ids in requests_by_delivery_mech.items():
566
+ data = await watch_for_mech_data_url(
567
+ request_ids=request_ids,
568
+ from_block=from_block,
569
+ mech_contract_address=delivery_mech,
570
+ mech_deliver_signature=deliver_signature,
549
571
  ledger_api=ledger_api,
550
- loop=loop,
572
+ timeout=timeout,
551
573
  )
552
- )
553
- tasks.append(on_chain_task)
574
+ results.update(data)
554
575
 
555
- if subgraph_url:
556
- print("Subgraph to be implemented")
576
+ return results
557
577
 
558
- async def _wait_for_tasks() -> Any: # type: ignore
559
- """Wait for tasks to finish."""
560
- (finished, *_), unfinished = await asyncio.wait(
561
- tasks,
562
- return_when=asyncio.FIRST_COMPLETED,
563
- )
564
- for task in unfinished:
565
- task.cancel()
566
- if unfinished:
567
- await asyncio.wait(unfinished)
568
- return finished.result()
578
+ marketplace_delivery_event_future = loop.create_task(
579
+ _wait_for_marketplace_delivery_event()
580
+ )
581
+ mech_data_future = loop.create_task(
582
+ _wait_for_mech_data(marketplace_delivery_event_future)
583
+ )
584
+
585
+ loop.run_until_complete(
586
+ asyncio.gather(marketplace_delivery_event_future, mech_data_future)
587
+ )
588
+ loop.close()
569
589
 
570
- result = loop.run_until_complete(_wait_for_tasks())
571
- return result
590
+ return mech_data_future.result()
572
591
 
573
592
 
574
593
  def wait_for_offchain_marketplace_data(mech_offchain_url: str, request_id: str) -> Any:
@@ -679,7 +698,6 @@ def marketplace_interact( # pylint: disable=too-many-arguments, too-many-locals
679
698
  tools: tuple = (),
680
699
  extra_attributes: Optional[Dict[str, Any]] = None,
681
700
  private_key_path: Optional[str] = None,
682
- confirmation_type: ConfirmationType = ConfirmationType.WAIT_FOR_BOTH,
683
701
  retries: Optional[int] = None,
684
702
  timeout: Optional[float] = None,
685
703
  sleep: Optional[float] = None,
@@ -704,8 +722,6 @@ def marketplace_interact( # pylint: disable=too-many-arguments, too-many-locals
704
722
  :type extra_attributes: Optional[Dict[str, Any]]
705
723
  :param private_key_path: The path to the private key file (optional).
706
724
  :type private_key_path: Optional[str]
707
- :param confirmation_type: The confirmation type for the interaction (default: ConfirmationType.WAIT_FOR_BOTH).
708
- :type confirmation_type: ConfirmationType
709
725
  :return: The data received from on-chain/off-chain.
710
726
  :param retries: Number of retries for sending a transaction
711
727
  :type retries: int
@@ -745,7 +761,7 @@ def marketplace_interact( # pylint: disable=too-many-arguments, too-many-locals
745
761
  ((k, type(v)) for k, v in config_values.items()),
746
762
  )(**config_values)
747
763
 
748
- contract_address = cast(
764
+ marketplace_contract_address = cast(
749
765
  str, mech_marketplace_request_config.mech_marketplace_contract
750
766
  )
751
767
 
@@ -755,7 +771,6 @@ def marketplace_interact( # pylint: disable=too-many-arguments, too-many-locals
755
771
  f"Private key file `{private_key_path}` does not exist!"
756
772
  )
757
773
 
758
- wss = websocket.create_connection(mech_config.wss_endpoint)
759
774
  crypto = EthereumCrypto(private_key_path=private_key_path)
760
775
  ledger_api = EthereumApi(**asdict(ledger_config))
761
776
 
@@ -763,7 +778,7 @@ def marketplace_interact( # pylint: disable=too-many-arguments, too-many-locals
763
778
  abi = json.load(f)
764
779
 
765
780
  mech_marketplace_contract = get_contract(
766
- contract_address=contract_address, abi=abi, ledger_api=ledger_api
781
+ contract_address=marketplace_contract_address, abi=abi, ledger_api=ledger_api
767
782
  )
768
783
 
769
784
  print("Fetching Mech Info...")
@@ -775,7 +790,6 @@ def marketplace_interact( # pylint: disable=too-many-arguments, too-many-locals
775
790
  service_id,
776
791
  max_delivery_rate,
777
792
  mech_payment_balance_tracker,
778
- mech_contract,
779
793
  ) = fetch_mech_info(
780
794
  ledger_api,
781
795
  mech_marketplace_contract,
@@ -784,23 +798,12 @@ def marketplace_interact( # pylint: disable=too-many-arguments, too-many-locals
784
798
  mech_marketplace_request_config.delivery_rate = max_delivery_rate
785
799
  mech_marketplace_request_config.payment_type = payment_type
786
800
 
787
- verify_tools(tools, service_id, chain_config)
788
-
789
- with open(IMECH_ABI_PATH, encoding="utf-8") as f:
790
- abi = json.load(f)
791
-
792
- (
793
- marketplace_request_event_signature,
794
- marketplace_deliver_event_signature,
795
- ) = get_event_signatures(abi=abi)
796
- register_event_handlers(
797
- wss=wss,
798
- contract_address=priority_mech_address,
799
- crypto=crypto,
800
- request_signature=marketplace_request_event_signature,
801
- deliver_signature=marketplace_deliver_event_signature,
801
+ mech_deliver_event_signature = fetch_mech_deliver_event_signature(
802
+ ledger_api, priority_mech_address
802
803
  )
803
804
 
805
+ verify_tools(tools, service_id, chain_config)
806
+
804
807
  if not use_prepaid:
805
808
  price = max_delivery_rate * num_requests
806
809
  if payment_type == PaymentType.TOKEN.value:
@@ -860,6 +863,12 @@ def marketplace_interact( # pylint: disable=too-many-arguments, too-many-locals
860
863
  # set price 0 to not send any msg.value in request transaction for nvm type mech
861
864
  price = 0
862
865
 
866
+ # from block to be used to search for onchain events
867
+ # and is selected before the request is sent
868
+ # so searching for deliver events in the logs will not be missed
869
+ w3 = ledger_api.api.eth
870
+ latest_block = w3.block_number
871
+
863
872
  if not use_offchain:
864
873
  print("Sending Mech Marketplace request...")
865
874
  transaction_digest = send_marketplace_request(
@@ -904,36 +913,36 @@ def marketplace_interact( # pylint: disable=too-many-arguments, too-many-locals
904
913
  )
905
914
  print("")
906
915
 
907
- for request_id, request_id_int in zip(request_ids, request_id_ints):
908
- data_url = wait_for_marketplace_data_url(
909
- request_id=request_id,
910
- wss=wss,
911
- mech_contract=mech_contract,
912
- subgraph_url=mech_config.subgraph_url,
913
- deliver_signature=marketplace_deliver_event_signature,
914
- ledger_api=ledger_api,
915
- crypto=crypto,
916
- confirmation_type=confirmation_type,
916
+ data_urls = wait_for_marketplace_data_url(
917
+ request_ids=request_ids,
918
+ from_block=latest_block,
919
+ marketplace_contract=mech_marketplace_contract,
920
+ deliver_signature=mech_deliver_event_signature,
921
+ ledger_api=ledger_api,
922
+ timeout=timeout,
923
+ )
924
+
925
+ if not data_urls:
926
+ print("Cannot find any data urls for the request(s)")
927
+ return None
928
+
929
+ if is_nvm_mech:
930
+ requester_total_balance_after = fetch_requester_nvm_subscription_balance(
931
+ requester,
932
+ ledger_api,
933
+ mech_payment_balance_tracker,
934
+ payment_type,
935
+ )
936
+ print(
937
+ f" - Sender Subscription balance after delivery: {requester_total_balance_after}"
917
938
  )
918
939
 
919
- if data_url:
920
- if is_nvm_mech:
921
- requester_total_balance_after = (
922
- fetch_requester_nvm_subscription_balance(
923
- requester,
924
- ledger_api,
925
- mech_payment_balance_tracker,
926
- payment_type,
927
- )
928
- )
929
- print(
930
- f" - Sender Subscription balance after delivery: {requester_total_balance_after}"
931
- )
932
-
933
- print(f" - Data arrived: {data_url}")
934
- data = requests.get(f"{data_url}/{request_id_int}", timeout=30).json()
935
- print(" - Data from agent:")
936
- print(json.dumps(data, indent=2))
940
+ for request_id, data_url in data_urls.items():
941
+ request_id_int = int.from_bytes(bytes.fromhex(request_id), byteorder="big")
942
+ print(f" - Data arrived: {data_url}")
943
+ data = requests.get(f"{data_url}/{request_id_int}", timeout=30).json()
944
+ print(" - Data from agent:")
945
+ print(json.dumps(data, indent=2))
937
946
  return None
938
947
 
939
948
  print("Sending Offchain Mech Marketplace request...")
@@ -31,26 +31,32 @@ from aea_ledger_ethereum import EthereumApi
31
31
  from web3.contract import Contract as Web3Contract
32
32
 
33
33
 
34
- def register_event_handlers(
34
+ IPFS_URL_TEMPLATE = "https://gateway.autonolas.tech/ipfs/f01701220{}"
35
+
36
+
37
+ def register_event_handlers( # pylint: disable=too-many-arguments
35
38
  wss: websocket.WebSocket,
36
- contract_address: str,
39
+ mech_contract_address: str,
40
+ marketplace_contract_address: str,
37
41
  crypto: Crypto,
38
- request_signature: str,
39
- deliver_signature: str,
42
+ mech_request_signature: str,
43
+ marketplace_deliver_signature: str,
40
44
  ) -> None:
41
45
  """
42
46
  Register event handlers.
43
47
 
44
48
  :param wss: The WebSocket connection object.
45
49
  :type wss: websocket.WebSocket
46
- :param contract_address: The address of the contract.
47
- :type contract_address: str
50
+ :param mech_contract_address: The address of the mech contract.
51
+ :type mech_contract_address: str
52
+ :param marketplace_contract_address: The address of the marketplace contract.
53
+ :type marketplace_contract_address: str
48
54
  :param crypto: The cryptographic object.
49
55
  :type crypto: Crypto
50
- :param request_signature: Topic signature for Request event
51
- :type request_signature: str
52
- :param deliver_signature: Topic signature for Deliver event
53
- :type deliver_signature: str
56
+ :param mech_request_signature: Topic signature for Request event
57
+ :type mech_request_signature: str
58
+ :param marketplace_deliver_signature: Topic signature for MarketplaceDelivery event
59
+ :type marketplace_deliver_signature: str
54
60
  """
55
61
 
56
62
  subscription_request = {
@@ -60,9 +66,9 @@ def register_event_handlers(
60
66
  "params": [
61
67
  "logs",
62
68
  {
63
- "address": contract_address,
69
+ "address": mech_contract_address,
64
70
  "topics": [
65
- request_signature,
71
+ mech_request_signature,
66
72
  ["0x" + "0" * 24 + crypto.address[2:]],
67
73
  ],
68
74
  },
@@ -73,16 +79,20 @@ def register_event_handlers(
73
79
 
74
80
  # registration confirmation
75
81
  _ = wss.recv()
76
- subscription_deliver = {
82
+
83
+ marketplace_subscription_deliver = {
77
84
  "jsonrpc": "2.0",
78
85
  "id": 1,
79
86
  "method": "eth_subscribe",
80
87
  "params": [
81
88
  "logs",
82
- {"address": contract_address, "topics": [deliver_signature]},
89
+ {
90
+ "address": marketplace_contract_address,
91
+ "topics": [marketplace_deliver_signature],
92
+ },
83
93
  ],
84
94
  }
85
- content = bytes(json.dumps(subscription_deliver), "utf-8")
95
+ content = bytes(json.dumps(marketplace_subscription_deliver), "utf-8")
86
96
  wss.send(content)
87
97
 
88
98
  # registration confirmation
@@ -224,62 +234,3 @@ async def watch_for_data_url_from_wss( # pylint: disable=too-many-arguments
224
234
  "Error: The WSS connection was likely closed by the remote party. Please, try using another WSS provider."
225
235
  )
226
236
  return None
227
-
228
-
229
- async def watch_for_marketplace_data_url_from_wss( # pylint: disable=too-many-arguments, unused-argument
230
- request_id: str,
231
- wss: websocket.WebSocket,
232
- mech_contract: Web3Contract,
233
- deliver_signature: str,
234
- ledger_api: EthereumApi,
235
- loop: asyncio.AbstractEventLoop,
236
- ) -> Any:
237
- """
238
- Watches for data on-chain.
239
-
240
- :param request_id: The ID of the request.
241
- :type request_id: str
242
- :param wss: The WebSocket connection object.
243
- :type wss: websocket.WebSocket
244
- :param mech_contract: The mech contract instance.
245
- :type mech_contract: Web3Contract
246
- :param deliver_signature: Topic signature for Deliver event
247
- :type deliver_signature: str
248
- :param ledger_api: The Ethereum API used for interacting with the ledger.
249
- :type ledger_api: EthereumApi
250
- :param loop: The event loop used for asynchronous operations.
251
- :type loop: asyncio.AbstractEventLoop
252
- :return: The data received from on-chain.
253
- :rtype: Any
254
- """
255
- with ThreadPoolExecutor() as executor:
256
- try:
257
- while True:
258
- msg = await loop.run_in_executor(executor=executor, func=wss.recv)
259
- data = json.loads(msg)
260
- tx_hash = data["params"]["result"]["transactionHash"]
261
- tx_receipt = await loop.run_in_executor(
262
- executor, wait_for_receipt, tx_hash, ledger_api
263
- )
264
-
265
- rich_logs = mech_contract.events.Deliver().process_receipt(tx_receipt)
266
- if len(rich_logs) == 0:
267
- print("Empty logs")
268
- return None
269
-
270
- data = rich_logs[0]["args"]
271
- tx_request_id = data["requestId"]
272
- deliver_data = data["data"]
273
-
274
- if request_id != tx_request_id.hex():
275
- continue
276
-
277
- return (
278
- f"https://gateway.autonolas.tech/ipfs/f01701220{deliver_data.hex()}"
279
- )
280
- except websocket.WebSocketConnectionClosedException as e:
281
- print(f"WebSocketConnectionClosedException {repr(e)}")
282
- print(
283
- "Error: The WSS connection was likely closed by the remote party. Please, try using another WSS provider."
284
- )
285
- return None
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "mech-client"
3
- version = "0.12.1"
3
+ version = "0.14.0"
4
4
  description = "Basic client to interact with a mech"
5
5
  authors = ["David Minarsch <david.minarsch@googlemail.com>"]
6
6
  readme = "README.md"
@@ -1,3 +0,0 @@
1
- """Mech client."""
2
-
3
- __version__ = "0.12.1"
File without changes
File without changes