olas-operate-middleware 0.6.2__py3-none-any.whl → 0.7.0__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.
- {olas_operate_middleware-0.6.2.dist-info → olas_operate_middleware-0.7.0.dist-info}/METADATA +3 -3
- {olas_operate_middleware-0.6.2.dist-info → olas_operate_middleware-0.7.0.dist-info}/RECORD +20 -19
- operate/bridge/{bridge.py → bridge_manager.py} +99 -62
- operate/bridge/providers/{lifi_bridge_provider.py → lifi_provider.py} +94 -81
- operate/bridge/providers/native_bridge_provider.py +124 -115
- operate/bridge/providers/{bridge_provider.py → provider.py} +132 -150
- operate/bridge/providers/relay_provider.py +442 -0
- operate/cli.py +72 -7
- operate/constants.py +1 -0
- operate/ledger/profiles.py +37 -1
- operate/quickstart/reset_configs.py +109 -0
- operate/quickstart/run_service.py +31 -5
- operate/quickstart/utils.py +6 -2
- operate/services/agent_runner.py +168 -91
- operate/services/manage.py +41 -11
- operate/services/service.py +10 -2
- operate/services/utils/mech.py +6 -4
- operate/data/contracts/service_staking_token/contract.yaml +0 -23
- {olas_operate_middleware-0.6.2.dist-info → olas_operate_middleware-0.7.0.dist-info}/LICENSE +0 -0
- {olas_operate_middleware-0.6.2.dist-info → olas_operate_middleware-0.7.0.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.6.2.dist-info → olas_operate_middleware-0.7.0.dist-info}/entry_points.txt +0 -0
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
#
|
|
19
19
|
# ------------------------------------------------------------------------------
|
|
20
|
-
"""
|
|
20
|
+
"""Provider."""
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
import copy
|
|
@@ -28,6 +28,7 @@ import typing as t
|
|
|
28
28
|
import uuid
|
|
29
29
|
from abc import ABC, abstractmethod
|
|
30
30
|
from dataclasses import dataclass
|
|
31
|
+
from math import ceil
|
|
31
32
|
|
|
32
33
|
from aea.crypto.base import LedgerApi
|
|
33
34
|
from aea.helpers.logging import setup_logger
|
|
@@ -46,21 +47,23 @@ from operate.resource import LocalResource
|
|
|
46
47
|
from operate.wallet.master import MasterWalletManager
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
GAS_ESTIMATE_FALLBACK_ADDRESSES = [
|
|
51
|
+
"0x000000000000000000000000000000000000dEaD",
|
|
52
|
+
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", # nosec
|
|
53
|
+
]
|
|
50
54
|
|
|
51
55
|
DEFAULT_MAX_QUOTE_RETRIES = 3
|
|
52
|
-
|
|
56
|
+
PROVIDER_REQUEST_PREFIX = "r-"
|
|
53
57
|
MESSAGE_QUOTE_ZERO = "Zero-amount quote requested."
|
|
54
58
|
MESSAGE_EXECUTION_SKIPPED = "Execution skipped."
|
|
55
59
|
MESSAGE_EXECUTION_FAILED = "Execution failed:"
|
|
56
|
-
MESSAGE_EXECUTION_FAILED_ETA = f"{MESSAGE_EXECUTION_FAILED}
|
|
60
|
+
MESSAGE_EXECUTION_FAILED_ETA = f"{MESSAGE_EXECUTION_FAILED} ETA exceeded."
|
|
57
61
|
MESSAGE_EXECUTION_FAILED_QUOTE_FAILED = f"{MESSAGE_EXECUTION_FAILED} quote failed."
|
|
58
|
-
MESSAGE_EXECUTION_FAILED_REVERTED =
|
|
59
|
-
f"{MESSAGE_EXECUTION_FAILED} bridge transaction reverted."
|
|
60
|
-
)
|
|
62
|
+
MESSAGE_EXECUTION_FAILED_REVERTED = f"{MESSAGE_EXECUTION_FAILED} transaction reverted."
|
|
61
63
|
MESSAGE_EXECUTION_FAILED_SETTLEMENT = (
|
|
62
64
|
f"{MESSAGE_EXECUTION_FAILED} transaction settlement failed."
|
|
63
65
|
)
|
|
66
|
+
MESSAGE_REQUIREMENTS_QUOTE_FAILED = "Cannot compute requirements for failed quote."
|
|
64
67
|
|
|
65
68
|
ERC20_APPROVE_SELECTOR = "0x095ea7b3" # First 4 bytes of Web3.keccak(text='approve(address,uint256)').hex()[:10]
|
|
66
69
|
|
|
@@ -71,7 +74,7 @@ GAS_ESTIMATE_BUFFER = 1.10
|
|
|
71
74
|
class QuoteData(LocalResource):
|
|
72
75
|
"""QuoteData"""
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
eta: t.Optional[int]
|
|
75
78
|
elapsed_time: float
|
|
76
79
|
message: t.Optional[str]
|
|
77
80
|
timestamp: int
|
|
@@ -90,8 +93,8 @@ class ExecutionData(LocalResource):
|
|
|
90
93
|
provider_data: t.Optional[t.Dict] # Provider-specific data
|
|
91
94
|
|
|
92
95
|
|
|
93
|
-
class
|
|
94
|
-
"""
|
|
96
|
+
class ProviderRequestStatus(str, enum.Enum):
|
|
97
|
+
"""ProviderRequestStatus"""
|
|
95
98
|
|
|
96
99
|
CREATED = "CREATED"
|
|
97
100
|
QUOTE_DONE = "QUOTE_DONE"
|
|
@@ -107,35 +110,34 @@ class BridgeRequestStatus(str, enum.Enum):
|
|
|
107
110
|
|
|
108
111
|
|
|
109
112
|
@dataclass
|
|
110
|
-
class
|
|
111
|
-
"""
|
|
113
|
+
class ProviderRequest(LocalResource):
|
|
114
|
+
"""ProviderRequest"""
|
|
112
115
|
|
|
113
116
|
params: t.Dict
|
|
114
|
-
|
|
117
|
+
provider_id: str
|
|
115
118
|
id: str
|
|
116
|
-
status:
|
|
119
|
+
status: ProviderRequestStatus
|
|
117
120
|
quote_data: t.Optional[QuoteData]
|
|
118
121
|
execution_data: t.Optional[ExecutionData]
|
|
119
122
|
|
|
120
123
|
|
|
121
|
-
class
|
|
122
|
-
"""(Abstract)
|
|
124
|
+
class Provider(ABC):
|
|
125
|
+
"""(Abstract) Provider.
|
|
123
126
|
|
|
124
127
|
Expected usage:
|
|
125
128
|
params = {...}
|
|
126
129
|
|
|
127
|
-
1. request =
|
|
128
|
-
2.
|
|
129
|
-
3.
|
|
130
|
-
4.
|
|
131
|
-
5.
|
|
130
|
+
1. request = provider.create_request(params)
|
|
131
|
+
2. provider.quote(request)
|
|
132
|
+
3. provider.requirements(request)
|
|
133
|
+
4. provider.execute(request)
|
|
134
|
+
5. provider.status_json(request)
|
|
132
135
|
|
|
133
136
|
Derived classes must implement the following methods:
|
|
134
137
|
- description
|
|
135
138
|
- quote
|
|
136
139
|
- _update_execution_status
|
|
137
|
-
-
|
|
138
|
-
- _get_bridge_tx
|
|
140
|
+
- _get_txs
|
|
139
141
|
- _get_explorer_link
|
|
140
142
|
"""
|
|
141
143
|
|
|
@@ -145,28 +147,28 @@ class BridgeProvider(ABC):
|
|
|
145
147
|
provider_id: str,
|
|
146
148
|
logger: t.Optional[logging.Logger] = None,
|
|
147
149
|
) -> None:
|
|
148
|
-
"""Initialize the
|
|
150
|
+
"""Initialize the provider."""
|
|
149
151
|
self.wallet_manager = wallet_manager
|
|
150
152
|
self.provider_id = provider_id
|
|
151
|
-
self.logger = logger or setup_logger(name="operate.bridge.
|
|
153
|
+
self.logger = logger or setup_logger(name="operate.bridge.providers.Provider")
|
|
152
154
|
|
|
153
155
|
def description(self) -> str:
|
|
154
|
-
"""Get a human-readable description of the
|
|
156
|
+
"""Get a human-readable description of the provider."""
|
|
155
157
|
return self.__class__.__name__
|
|
156
158
|
|
|
157
|
-
def _validate(self,
|
|
158
|
-
"""Validate
|
|
159
|
-
if
|
|
159
|
+
def _validate(self, provider_request: ProviderRequest) -> None:
|
|
160
|
+
"""Validate that the request was created by this provider."""
|
|
161
|
+
if provider_request.provider_id != self.provider_id:
|
|
160
162
|
raise ValueError(
|
|
161
|
-
f"
|
|
163
|
+
f"Request provider id {provider_request.provider_id} does not match the provider id {self.provider_id}"
|
|
162
164
|
)
|
|
163
165
|
|
|
164
166
|
def can_handle_request(self, params: t.Dict) -> bool:
|
|
165
|
-
"""Returns 'true' if the
|
|
167
|
+
"""Returns 'true' if the provider can handle a request for 'params'."""
|
|
166
168
|
|
|
167
169
|
if "from" not in params or "to" not in params:
|
|
168
170
|
self.logger.error(
|
|
169
|
-
"[
|
|
171
|
+
"[PROVIDER] Invalid input: All requests must contain exactly one 'from' and one 'to' sender."
|
|
170
172
|
)
|
|
171
173
|
return False
|
|
172
174
|
|
|
@@ -180,7 +182,7 @@ class BridgeProvider(ABC):
|
|
|
180
182
|
or "token" not in from_
|
|
181
183
|
):
|
|
182
184
|
self.logger.error(
|
|
183
|
-
"[
|
|
185
|
+
"[PROVIDER] Invalid input: 'from' must contain 'chain', 'address', and 'token'."
|
|
184
186
|
)
|
|
185
187
|
return False
|
|
186
188
|
|
|
@@ -192,17 +194,17 @@ class BridgeProvider(ABC):
|
|
|
192
194
|
or "amount" not in to
|
|
193
195
|
):
|
|
194
196
|
self.logger.error(
|
|
195
|
-
"[
|
|
197
|
+
"[PROVIDER] Invalid input: 'to' must contain 'chain', 'address', 'token', and 'amount'."
|
|
196
198
|
)
|
|
197
199
|
return False
|
|
198
200
|
|
|
199
201
|
return True
|
|
200
202
|
|
|
201
|
-
def create_request(self, params: t.Dict) ->
|
|
202
|
-
"""Create a
|
|
203
|
+
def create_request(self, params: t.Dict) -> ProviderRequest:
|
|
204
|
+
"""Create a request."""
|
|
203
205
|
|
|
204
206
|
if not self.can_handle_request(params):
|
|
205
|
-
raise ValueError("Invalid input: Cannot process
|
|
207
|
+
raise ValueError("Invalid input: Cannot process request.")
|
|
206
208
|
|
|
207
209
|
w3 = Web3()
|
|
208
210
|
params = copy.deepcopy(params)
|
|
@@ -212,18 +214,18 @@ class BridgeProvider(ABC):
|
|
|
212
214
|
params["to"]["token"] = w3.to_checksum_address(params["to"]["token"])
|
|
213
215
|
params["to"]["amount"] = int(params["to"]["amount"])
|
|
214
216
|
|
|
215
|
-
return
|
|
217
|
+
return ProviderRequest(
|
|
216
218
|
params=params,
|
|
217
|
-
|
|
218
|
-
id=f"{
|
|
219
|
+
provider_id=self.provider_id,
|
|
220
|
+
id=f"{PROVIDER_REQUEST_PREFIX}{uuid.uuid4()}",
|
|
219
221
|
quote_data=None,
|
|
220
222
|
execution_data=None,
|
|
221
|
-
status=
|
|
223
|
+
status=ProviderRequestStatus.CREATED,
|
|
222
224
|
)
|
|
223
225
|
|
|
224
|
-
def _from_ledger_api(self,
|
|
226
|
+
def _from_ledger_api(self, provider_request: ProviderRequest) -> LedgerApi:
|
|
225
227
|
"""Get the from ledger api."""
|
|
226
|
-
from_chain =
|
|
228
|
+
from_chain = provider_request.params["from"]["chain"]
|
|
227
229
|
chain = Chain(from_chain)
|
|
228
230
|
wallet = self.wallet_manager.load(chain.ledger_type)
|
|
229
231
|
ledger_api = wallet.ledger_api(chain)
|
|
@@ -234,9 +236,9 @@ class BridgeProvider(ABC):
|
|
|
234
236
|
|
|
235
237
|
return ledger_api
|
|
236
238
|
|
|
237
|
-
def _to_ledger_api(self,
|
|
239
|
+
def _to_ledger_api(self, provider_request: ProviderRequest) -> LedgerApi:
|
|
238
240
|
"""Get the from ledger api."""
|
|
239
|
-
from_chain =
|
|
241
|
+
from_chain = provider_request.params["to"]["chain"]
|
|
240
242
|
chain = Chain(from_chain)
|
|
241
243
|
wallet = self.wallet_manager.load(chain.ledger_type)
|
|
242
244
|
ledger_api = wallet.ledger_api(chain)
|
|
@@ -248,41 +250,29 @@ class BridgeProvider(ABC):
|
|
|
248
250
|
return ledger_api
|
|
249
251
|
|
|
250
252
|
@abstractmethod
|
|
251
|
-
def quote(self,
|
|
253
|
+
def quote(self, provider_request: ProviderRequest) -> None:
|
|
252
254
|
"""Update the request with the quote."""
|
|
253
255
|
raise NotImplementedError()
|
|
254
256
|
|
|
255
257
|
@abstractmethod
|
|
256
|
-
def
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
@abstractmethod
|
|
261
|
-
def _get_bridge_tx(self, bridge_request: BridgeRequest) -> t.Optional[t.Dict]:
|
|
262
|
-
"""Get the bridge transaction."""
|
|
258
|
+
def _get_txs(
|
|
259
|
+
self, provider_request: ProviderRequest, *args: t.Any, **kwargs: t.Any
|
|
260
|
+
) -> t.List[t.Tuple[str, t.Dict]]:
|
|
261
|
+
"""Get the sorted list of transactions to execute the quote."""
|
|
263
262
|
raise NotImplementedError()
|
|
264
263
|
|
|
265
|
-
def
|
|
266
|
-
"""Gets the
|
|
267
|
-
self.logger.info(
|
|
268
|
-
f"[BRIDGE PROVIDER] Bridge requirements for request {bridge_request.id}."
|
|
269
|
-
)
|
|
264
|
+
def requirements(self, provider_request: ProviderRequest) -> t.Dict:
|
|
265
|
+
"""Gets the requirements to execute the quote, with updated gas estimation."""
|
|
266
|
+
self.logger.info(f"[PROVIDER] Requirements for request {provider_request.id}.")
|
|
270
267
|
|
|
271
|
-
self._validate(
|
|
268
|
+
self._validate(provider_request)
|
|
272
269
|
|
|
273
|
-
from_chain =
|
|
274
|
-
from_address =
|
|
275
|
-
from_token =
|
|
276
|
-
from_ledger_api = self._from_ledger_api(
|
|
270
|
+
from_chain = provider_request.params["from"]["chain"]
|
|
271
|
+
from_address = provider_request.params["from"]["address"]
|
|
272
|
+
from_token = provider_request.params["from"]["token"]
|
|
273
|
+
from_ledger_api = self._from_ledger_api(provider_request)
|
|
277
274
|
|
|
278
|
-
txs =
|
|
279
|
-
|
|
280
|
-
approve_tx = self._get_approve_tx(bridge_request)
|
|
281
|
-
if approve_tx:
|
|
282
|
-
txs.append(("approve_tx", approve_tx))
|
|
283
|
-
bridge_tx = self._get_bridge_tx(bridge_request)
|
|
284
|
-
if bridge_tx:
|
|
285
|
-
txs.append(("bridge_tx", bridge_tx))
|
|
275
|
+
txs = self._get_txs(provider_request)
|
|
286
276
|
|
|
287
277
|
if not txs:
|
|
288
278
|
return {
|
|
@@ -300,7 +290,7 @@ class BridgeProvider(ABC):
|
|
|
300
290
|
|
|
301
291
|
for tx_label, tx in txs:
|
|
302
292
|
self.logger.debug(
|
|
303
|
-
f"[
|
|
293
|
+
f"[PROVIDER] Processing transaction {tx_label} for request {provider_request.id}."
|
|
304
294
|
)
|
|
305
295
|
self._update_with_gas_pricing(tx, from_ledger_api)
|
|
306
296
|
gas_key = "gasPrice" if "gasPrice" in tx else "maxFeePerGas"
|
|
@@ -310,11 +300,11 @@ class BridgeProvider(ABC):
|
|
|
310
300
|
total_native += tx_value + gas_fees
|
|
311
301
|
|
|
312
302
|
self.logger.debug(
|
|
313
|
-
f"[
|
|
303
|
+
f"[PROVIDER] Transaction {gas_key}={tx.get(gas_key, 0)} maxPriorityFeePerGas={tx.get('maxPriorityFeePerGas', -1)} gas={tx['gas']} {gas_fees=} {tx_value=}"
|
|
314
304
|
)
|
|
315
|
-
self.logger.debug(f"[
|
|
305
|
+
self.logger.debug(f"[PROVIDER] {from_ledger_api.api.eth.gas_price=}")
|
|
316
306
|
self.logger.debug(
|
|
317
|
-
f"[
|
|
307
|
+
f"[PROVIDER] {from_ledger_api.api.eth.get_block('latest').baseFeePerGas=}"
|
|
318
308
|
)
|
|
319
309
|
|
|
320
310
|
if tx.get("to", "").lower() == from_token.lower() and tx.get(
|
|
@@ -327,7 +317,7 @@ class BridgeProvider(ABC):
|
|
|
327
317
|
raise RuntimeError("Malformed ERC20 approve transaction.") from e
|
|
328
318
|
|
|
329
319
|
self.logger.info(
|
|
330
|
-
f"[
|
|
320
|
+
f"[PROVIDER] Total gas fees for request {provider_request.id}: {total_gas_fees} native units."
|
|
331
321
|
)
|
|
332
322
|
|
|
333
323
|
result = {
|
|
@@ -343,18 +333,14 @@ class BridgeProvider(ABC):
|
|
|
343
333
|
|
|
344
334
|
return result
|
|
345
335
|
|
|
346
|
-
def execute(self,
|
|
336
|
+
def execute(self, provider_request: ProviderRequest) -> None:
|
|
347
337
|
"""Execute the request."""
|
|
348
|
-
self.logger.info(
|
|
349
|
-
f"[BRIDGE PROVIDER] Executing bridge request {bridge_request.id}."
|
|
350
|
-
)
|
|
338
|
+
self.logger.info(f"[PROVIDER] Executing request {provider_request.id}.")
|
|
351
339
|
|
|
352
|
-
self._validate(
|
|
340
|
+
self._validate(provider_request)
|
|
353
341
|
|
|
354
|
-
if
|
|
355
|
-
self.logger.info(
|
|
356
|
-
f"[BRIDGE PROVIDER] {MESSAGE_EXECUTION_FAILED_QUOTE_FAILED}."
|
|
357
|
-
)
|
|
342
|
+
if provider_request.status in (ProviderRequestStatus.QUOTE_FAILED):
|
|
343
|
+
self.logger.info(f"[PROVIDER] {MESSAGE_EXECUTION_FAILED_QUOTE_FAILED}.")
|
|
358
344
|
execution_data = ExecutionData(
|
|
359
345
|
elapsed_time=0,
|
|
360
346
|
message=f"{MESSAGE_EXECUTION_FAILED_QUOTE_FAILED}",
|
|
@@ -363,61 +349,52 @@ class BridgeProvider(ABC):
|
|
|
363
349
|
to_tx_hash=None,
|
|
364
350
|
provider_data=None,
|
|
365
351
|
)
|
|
366
|
-
|
|
367
|
-
|
|
352
|
+
provider_request.execution_data = execution_data
|
|
353
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_FAILED
|
|
368
354
|
return
|
|
369
355
|
|
|
370
|
-
if
|
|
356
|
+
if provider_request.status not in (ProviderRequestStatus.QUOTE_DONE,):
|
|
371
357
|
raise RuntimeError(
|
|
372
|
-
f"Cannot execute
|
|
358
|
+
f"Cannot execute request {provider_request.id} with status {provider_request.status}."
|
|
373
359
|
)
|
|
374
|
-
if not
|
|
360
|
+
if not provider_request.quote_data:
|
|
375
361
|
raise RuntimeError(
|
|
376
|
-
f"Cannot execute
|
|
362
|
+
f"Cannot execute request {provider_request.id}: quote data not present."
|
|
377
363
|
)
|
|
378
|
-
if
|
|
364
|
+
if provider_request.execution_data:
|
|
379
365
|
raise RuntimeError(
|
|
380
|
-
f"Cannot execute
|
|
366
|
+
f"Cannot execute request {provider_request.id}: execution data already present."
|
|
381
367
|
)
|
|
382
368
|
|
|
383
|
-
txs =
|
|
384
|
-
|
|
385
|
-
approve_tx = self._get_approve_tx(bridge_request)
|
|
386
|
-
if approve_tx:
|
|
387
|
-
txs.append(("approve_tx", approve_tx))
|
|
388
|
-
bridge_tx = self._get_bridge_tx(bridge_request)
|
|
389
|
-
if bridge_tx:
|
|
390
|
-
txs.append(("bridge_tx", bridge_tx))
|
|
369
|
+
txs = self._get_txs(provider_request)
|
|
391
370
|
|
|
392
371
|
if not txs:
|
|
393
372
|
self.logger.info(
|
|
394
|
-
f"[
|
|
373
|
+
f"[PROVIDER] {MESSAGE_EXECUTION_SKIPPED} ({provider_request.status=})"
|
|
395
374
|
)
|
|
396
375
|
execution_data = ExecutionData(
|
|
397
376
|
elapsed_time=0,
|
|
398
|
-
message=f"{MESSAGE_EXECUTION_SKIPPED} ({
|
|
377
|
+
message=f"{MESSAGE_EXECUTION_SKIPPED} ({provider_request.status=})",
|
|
399
378
|
timestamp=int(time.time()),
|
|
400
379
|
from_tx_hash=None,
|
|
401
380
|
to_tx_hash=None,
|
|
402
381
|
provider_data=None,
|
|
403
382
|
)
|
|
404
|
-
|
|
405
|
-
|
|
383
|
+
provider_request.execution_data = execution_data
|
|
384
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_DONE
|
|
406
385
|
return
|
|
407
386
|
|
|
408
387
|
try:
|
|
409
|
-
self.logger.info(
|
|
410
|
-
f"[BRIDGE PROVIDER] Executing bridge request {bridge_request.id}."
|
|
411
|
-
)
|
|
388
|
+
self.logger.info(f"[PROVIDER] Executing request {provider_request.id}.")
|
|
412
389
|
timestamp = time.time()
|
|
413
|
-
chain = Chain(
|
|
414
|
-
from_address =
|
|
390
|
+
chain = Chain(provider_request.params["from"]["chain"])
|
|
391
|
+
from_address = provider_request.params["from"]["address"]
|
|
415
392
|
wallet = self.wallet_manager.load(chain.ledger_type)
|
|
416
|
-
from_ledger_api = self._from_ledger_api(
|
|
393
|
+
from_ledger_api = self._from_ledger_api(provider_request)
|
|
417
394
|
tx_settler = TxSettler(
|
|
418
395
|
ledger_api=from_ledger_api,
|
|
419
396
|
crypto=wallet.crypto,
|
|
420
|
-
chain_type=Chain(
|
|
397
|
+
chain_type=Chain(provider_request.params["from"]["chain"]),
|
|
421
398
|
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
422
399
|
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
423
400
|
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
@@ -425,7 +402,7 @@ class BridgeProvider(ABC):
|
|
|
425
402
|
tx_hashes = []
|
|
426
403
|
|
|
427
404
|
for tx_label, tx in txs:
|
|
428
|
-
self.logger.info(f"[
|
|
405
|
+
self.logger.info(f"[PROVIDER] Executing transaction {tx_label}.")
|
|
429
406
|
nonce = from_ledger_api.api.eth.get_transaction_count(from_address)
|
|
430
407
|
tx["nonce"] = nonce # TODO: backport to TxSettler
|
|
431
408
|
setattr( # noqa: B010
|
|
@@ -437,7 +414,7 @@ class BridgeProvider(ABC):
|
|
|
437
414
|
kwargs={},
|
|
438
415
|
dry_run=False,
|
|
439
416
|
)
|
|
440
|
-
self.logger.info(f"[
|
|
417
|
+
self.logger.info(f"[PROVIDER] Transaction {tx_label} settled.")
|
|
441
418
|
tx_hashes.append(tx_receipt.get("transactionHash", "").hex())
|
|
442
419
|
|
|
443
420
|
execution_data = ExecutionData(
|
|
@@ -448,17 +425,17 @@ class BridgeProvider(ABC):
|
|
|
448
425
|
to_tx_hash=None,
|
|
449
426
|
provider_data=None,
|
|
450
427
|
)
|
|
451
|
-
|
|
428
|
+
provider_request.execution_data = execution_data
|
|
452
429
|
if len(tx_hashes) == len(txs):
|
|
453
|
-
|
|
430
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_PENDING
|
|
454
431
|
else:
|
|
455
|
-
|
|
432
|
+
provider_request.execution_data.message = (
|
|
456
433
|
MESSAGE_EXECUTION_FAILED_SETTLEMENT
|
|
457
434
|
)
|
|
458
|
-
|
|
435
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_FAILED
|
|
459
436
|
|
|
460
437
|
except Exception as e: # pylint: disable=broad-except
|
|
461
|
-
self.logger.error(f"[
|
|
438
|
+
self.logger.error(f"[PROVIDER] Error executing request: {e}")
|
|
462
439
|
execution_data = ExecutionData(
|
|
463
440
|
elapsed_time=time.time() - timestamp,
|
|
464
441
|
message=f"{MESSAGE_EXECUTION_FAILED} {str(e)}",
|
|
@@ -467,44 +444,44 @@ class BridgeProvider(ABC):
|
|
|
467
444
|
to_tx_hash=None,
|
|
468
445
|
provider_data=None,
|
|
469
446
|
)
|
|
470
|
-
|
|
471
|
-
|
|
447
|
+
provider_request.execution_data = execution_data
|
|
448
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_FAILED
|
|
472
449
|
|
|
473
450
|
@abstractmethod
|
|
474
|
-
def _update_execution_status(self,
|
|
451
|
+
def _update_execution_status(self, provider_request: ProviderRequest) -> None:
|
|
475
452
|
"""Update the execution status."""
|
|
476
453
|
raise NotImplementedError()
|
|
477
454
|
|
|
478
455
|
@abstractmethod
|
|
479
|
-
def _get_explorer_link(self,
|
|
456
|
+
def _get_explorer_link(self, provider_request: ProviderRequest) -> t.Optional[str]:
|
|
480
457
|
"""Get the explorer link for a transaction."""
|
|
481
458
|
raise NotImplementedError()
|
|
482
459
|
|
|
483
|
-
def status_json(self,
|
|
460
|
+
def status_json(self, provider_request: ProviderRequest) -> t.Dict:
|
|
484
461
|
"""JSON representation of the status."""
|
|
485
|
-
self._validate(
|
|
462
|
+
self._validate(provider_request)
|
|
486
463
|
|
|
487
|
-
if
|
|
488
|
-
self._update_execution_status(
|
|
464
|
+
if provider_request.execution_data and provider_request.quote_data:
|
|
465
|
+
self._update_execution_status(provider_request)
|
|
489
466
|
tx_hash = None
|
|
490
|
-
if
|
|
491
|
-
tx_hash =
|
|
467
|
+
if provider_request.execution_data.from_tx_hash:
|
|
468
|
+
tx_hash = provider_request.execution_data.from_tx_hash
|
|
492
469
|
|
|
493
470
|
return {
|
|
494
|
-
"eta":
|
|
495
|
-
"explorer_link": self._get_explorer_link(
|
|
496
|
-
"message":
|
|
497
|
-
"status":
|
|
471
|
+
"eta": provider_request.quote_data.eta,
|
|
472
|
+
"explorer_link": self._get_explorer_link(provider_request),
|
|
473
|
+
"message": provider_request.execution_data.message,
|
|
474
|
+
"status": provider_request.status.value,
|
|
498
475
|
"tx_hash": tx_hash,
|
|
499
476
|
}
|
|
500
|
-
if
|
|
477
|
+
if provider_request.quote_data:
|
|
501
478
|
return {
|
|
502
|
-
"eta":
|
|
503
|
-
"message":
|
|
504
|
-
"status":
|
|
479
|
+
"eta": provider_request.quote_data.eta,
|
|
480
|
+
"message": provider_request.quote_data.message,
|
|
481
|
+
"status": provider_request.status.value,
|
|
505
482
|
}
|
|
506
483
|
|
|
507
|
-
return {"message": None, "status":
|
|
484
|
+
return {"message": None, "status": provider_request.status.value}
|
|
508
485
|
|
|
509
486
|
@staticmethod
|
|
510
487
|
def _tx_timestamp(tx_hash: str, ledger_api: LedgerApi) -> int:
|
|
@@ -535,19 +512,24 @@ class BridgeProvider(ABC):
|
|
|
535
512
|
# TODO backport to open aea/autonomy
|
|
536
513
|
@staticmethod
|
|
537
514
|
def _update_with_gas_estimate(tx: t.Dict, ledger_api: LedgerApi) -> None:
|
|
515
|
+
print(
|
|
516
|
+
f"[PROVIDER] Trying to update transaction gas {tx['from']=} {tx['gas']=}."
|
|
517
|
+
)
|
|
518
|
+
original_from = tx["from"]
|
|
538
519
|
original_gas = tx.get("gas", 1)
|
|
539
|
-
tx["gas"] = 1
|
|
540
|
-
ledger_api.update_with_gas_estimate(tx)
|
|
541
520
|
|
|
542
|
-
|
|
543
|
-
|
|
521
|
+
for address in [original_from] + GAS_ESTIMATE_FALLBACK_ADDRESSES:
|
|
522
|
+
tx["from"] = address
|
|
523
|
+
tx["gas"] = 1
|
|
524
|
+
ledger_api.update_with_gas_estimate(tx)
|
|
525
|
+
if tx["gas"] > 1:
|
|
526
|
+
print(
|
|
527
|
+
f"[PROVIDER] Gas estimated successfully {tx['from']=} {tx['gas']=}."
|
|
528
|
+
)
|
|
529
|
+
break
|
|
544
530
|
|
|
545
|
-
original_from = tx["from"]
|
|
546
|
-
tx["from"] = PLACEHOLDER_NATIVE_TOKEN_ADDRESS
|
|
547
|
-
ledger_api.update_with_gas_estimate(tx)
|
|
548
531
|
tx["from"] = original_from
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
tx["gas"] = original_gas
|
|
532
|
+
if tx["gas"] == 1:
|
|
533
|
+
tx["gas"] = original_gas
|
|
534
|
+
print(f"[PROVIDER] Unable to estimate gas. Restored {tx['gas']=}.")
|
|
535
|
+
tx["gas"] = ceil(tx["gas"] * GAS_ESTIMATE_BUFFER)
|