olas-operate-middleware 0.6.3__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.3.dist-info → olas_operate_middleware-0.7.0.dist-info}/METADATA +1 -1
- {olas_operate_middleware-0.6.3.dist-info → olas_operate_middleware-0.7.0.dist-info}/RECORD +16 -15
- 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 +28 -7
- operate/constants.py +1 -0
- operate/ledger/profiles.py +37 -1
- operate/quickstart/run_service.py +31 -1
- operate/services/agent_runner.py +168 -91
- operate/services/manage.py +31 -4
- {olas_operate_middleware-0.6.3.dist-info → olas_operate_middleware-0.7.0.dist-info}/LICENSE +0 -0
- {olas_operate_middleware-0.6.3.dist-info → olas_operate_middleware-0.7.0.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.6.3.dist-info → olas_operate_middleware-0.7.0.dist-info}/entry_points.txt +0 -0
|
@@ -17,26 +17,24 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
#
|
|
19
19
|
# ------------------------------------------------------------------------------
|
|
20
|
-
"""LI.FI
|
|
20
|
+
"""LI.FI provider."""
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
import enum
|
|
24
24
|
import time
|
|
25
25
|
import typing as t
|
|
26
26
|
from http import HTTPStatus
|
|
27
|
-
from math import ceil
|
|
28
27
|
from urllib.parse import urlencode
|
|
29
28
|
|
|
30
29
|
import requests
|
|
31
30
|
from autonomy.chain.base import registry_contracts
|
|
32
31
|
|
|
33
|
-
from operate.bridge.providers.
|
|
34
|
-
BridgeProvider,
|
|
35
|
-
BridgeRequest,
|
|
36
|
-
BridgeRequestStatus,
|
|
32
|
+
from operate.bridge.providers.provider import (
|
|
37
33
|
DEFAULT_MAX_QUOTE_RETRIES,
|
|
38
|
-
GAS_ESTIMATE_BUFFER,
|
|
39
34
|
MESSAGE_QUOTE_ZERO,
|
|
35
|
+
Provider,
|
|
36
|
+
ProviderRequest,
|
|
37
|
+
ProviderRequestStatus,
|
|
40
38
|
QuoteData,
|
|
41
39
|
)
|
|
42
40
|
from operate.constants import ZERO_ADDRESS
|
|
@@ -61,50 +59,50 @@ class LiFiTransactionStatus(str, enum.Enum):
|
|
|
61
59
|
return self.value
|
|
62
60
|
|
|
63
61
|
|
|
64
|
-
class
|
|
65
|
-
"""LI.FI
|
|
62
|
+
class LiFiProvider(Provider):
|
|
63
|
+
"""LI.FI provider."""
|
|
66
64
|
|
|
67
65
|
def description(self) -> str:
|
|
68
|
-
"""Get a human-readable description of the
|
|
66
|
+
"""Get a human-readable description of the provider."""
|
|
69
67
|
return "LI.FI Bridge & DEX Aggregation Protocol https://li.fi/"
|
|
70
68
|
|
|
71
|
-
def quote(self,
|
|
69
|
+
def quote(self, provider_request: ProviderRequest) -> None:
|
|
72
70
|
"""Update the request with the quote."""
|
|
73
|
-
self._validate(
|
|
71
|
+
self._validate(provider_request)
|
|
74
72
|
|
|
75
|
-
if
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
if provider_request.status not in (
|
|
74
|
+
ProviderRequestStatus.CREATED,
|
|
75
|
+
ProviderRequestStatus.QUOTE_DONE,
|
|
76
|
+
ProviderRequestStatus.QUOTE_FAILED,
|
|
79
77
|
):
|
|
80
78
|
raise RuntimeError(
|
|
81
|
-
f"Cannot quote
|
|
79
|
+
f"Cannot quote request {provider_request.id} with status {provider_request.status}."
|
|
82
80
|
)
|
|
83
81
|
|
|
84
|
-
if
|
|
82
|
+
if provider_request.execution_data:
|
|
85
83
|
raise RuntimeError(
|
|
86
|
-
f"Cannot quote
|
|
84
|
+
f"Cannot quote request {provider_request.id}: execution already present."
|
|
87
85
|
)
|
|
88
86
|
|
|
89
|
-
from_chain =
|
|
90
|
-
from_address =
|
|
91
|
-
from_token =
|
|
92
|
-
to_chain =
|
|
93
|
-
to_address =
|
|
94
|
-
to_token =
|
|
95
|
-
to_amount =
|
|
87
|
+
from_chain = provider_request.params["from"]["chain"]
|
|
88
|
+
from_address = provider_request.params["from"]["address"]
|
|
89
|
+
from_token = provider_request.params["from"]["token"]
|
|
90
|
+
to_chain = provider_request.params["to"]["chain"]
|
|
91
|
+
to_address = provider_request.params["to"]["address"]
|
|
92
|
+
to_token = provider_request.params["to"]["token"]
|
|
93
|
+
to_amount = provider_request.params["to"]["amount"]
|
|
96
94
|
|
|
97
95
|
if to_amount == 0:
|
|
98
|
-
self.logger.info(f"[LI.FI
|
|
96
|
+
self.logger.info(f"[LI.FI PROVIDER] {MESSAGE_QUOTE_ZERO}")
|
|
99
97
|
quote_data = QuoteData(
|
|
100
|
-
|
|
98
|
+
eta=0,
|
|
101
99
|
elapsed_time=0,
|
|
102
100
|
message=MESSAGE_QUOTE_ZERO,
|
|
103
101
|
provider_data=None,
|
|
104
102
|
timestamp=int(time.time()),
|
|
105
103
|
)
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
provider_request.quote_data = quote_data
|
|
105
|
+
provider_request.status = ProviderRequestStatus.QUOTE_DONE
|
|
108
106
|
return
|
|
109
107
|
|
|
110
108
|
url = "https://li.quest/v1/quote/toAmount"
|
|
@@ -122,14 +120,14 @@ class LiFiBridgeProvider(BridgeProvider):
|
|
|
122
120
|
for attempt in range(1, DEFAULT_MAX_QUOTE_RETRIES + 1):
|
|
123
121
|
start = time.time()
|
|
124
122
|
try:
|
|
125
|
-
self.logger.info(f"[LI.FI
|
|
123
|
+
self.logger.info(f"[LI.FI PROVIDER] GET {url}?{urlencode(params)}")
|
|
126
124
|
response = requests.get(
|
|
127
125
|
url=url, headers=headers, params=params, timeout=30
|
|
128
126
|
)
|
|
129
127
|
response.raise_for_status()
|
|
130
128
|
response_json = response.json()
|
|
131
129
|
quote_data = QuoteData(
|
|
132
|
-
|
|
130
|
+
eta=LIFI_DEFAULT_ETA,
|
|
133
131
|
elapsed_time=time.time() - start,
|
|
134
132
|
message=None,
|
|
135
133
|
provider_data={
|
|
@@ -139,15 +137,15 @@ class LiFiBridgeProvider(BridgeProvider):
|
|
|
139
137
|
},
|
|
140
138
|
timestamp=int(time.time()),
|
|
141
139
|
)
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
provider_request.quote_data = quote_data
|
|
141
|
+
provider_request.status = ProviderRequestStatus.QUOTE_DONE
|
|
144
142
|
return
|
|
145
143
|
except requests.Timeout as e:
|
|
146
144
|
self.logger.warning(
|
|
147
|
-
f"[LI.FI
|
|
145
|
+
f"[LI.FI PROVIDER] Timeout request on attempt {attempt}/{DEFAULT_MAX_QUOTE_RETRIES}: {e}."
|
|
148
146
|
)
|
|
149
147
|
quote_data = QuoteData(
|
|
150
|
-
|
|
148
|
+
eta=None,
|
|
151
149
|
elapsed_time=time.time() - start,
|
|
152
150
|
message=str(e),
|
|
153
151
|
provider_data={
|
|
@@ -159,11 +157,11 @@ class LiFiBridgeProvider(BridgeProvider):
|
|
|
159
157
|
)
|
|
160
158
|
except requests.RequestException as e:
|
|
161
159
|
self.logger.warning(
|
|
162
|
-
f"[LI.FI
|
|
160
|
+
f"[LI.FI PROVIDER] Request failed on attempt {attempt}/{DEFAULT_MAX_QUOTE_RETRIES}: {e}."
|
|
163
161
|
)
|
|
164
162
|
response_json = response.json()
|
|
165
163
|
quote_data = QuoteData(
|
|
166
|
-
|
|
164
|
+
eta=None,
|
|
167
165
|
elapsed_time=time.time() - start,
|
|
168
166
|
message=response_json.get("message") or str(e),
|
|
169
167
|
provider_data={
|
|
@@ -177,10 +175,10 @@ class LiFiBridgeProvider(BridgeProvider):
|
|
|
177
175
|
)
|
|
178
176
|
except Exception as e: # pylint:disable=broad-except
|
|
179
177
|
self.logger.warning(
|
|
180
|
-
f"[LI.FI
|
|
178
|
+
f"[LI.FI PROVIDER] Request failed on attempt {attempt}/{DEFAULT_MAX_QUOTE_RETRIES}: {e}."
|
|
181
179
|
)
|
|
182
180
|
quote_data = QuoteData(
|
|
183
|
-
|
|
181
|
+
eta=None,
|
|
184
182
|
elapsed_time=time.time() - start,
|
|
185
183
|
message=str(e),
|
|
186
184
|
provider_data={
|
|
@@ -192,24 +190,24 @@ class LiFiBridgeProvider(BridgeProvider):
|
|
|
192
190
|
)
|
|
193
191
|
if attempt >= DEFAULT_MAX_QUOTE_RETRIES:
|
|
194
192
|
self.logger.error(
|
|
195
|
-
f"[LI.FI
|
|
193
|
+
f"[LI.FI PROVIDER] Request failed after {DEFAULT_MAX_QUOTE_RETRIES} attempts."
|
|
196
194
|
)
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
provider_request.quote_data = quote_data
|
|
196
|
+
provider_request.status = ProviderRequestStatus.QUOTE_FAILED
|
|
199
197
|
return
|
|
200
198
|
|
|
201
199
|
time.sleep(2)
|
|
202
200
|
|
|
203
|
-
def _get_approve_tx(self,
|
|
201
|
+
def _get_approve_tx(self, provider_request: ProviderRequest) -> t.Optional[t.Dict]:
|
|
204
202
|
"""Get the approve transaction."""
|
|
205
203
|
self.logger.info(
|
|
206
|
-
f"[LI.FI
|
|
204
|
+
f"[LI.FI PROVIDER] Get appprove transaction for request {provider_request.id}."
|
|
207
205
|
)
|
|
208
206
|
|
|
209
|
-
if
|
|
207
|
+
if provider_request.params["to"]["amount"] == 0:
|
|
210
208
|
return None
|
|
211
209
|
|
|
212
|
-
quote_data =
|
|
210
|
+
quote_data = provider_request.quote_data
|
|
213
211
|
if not quote_data:
|
|
214
212
|
return None
|
|
215
213
|
|
|
@@ -232,7 +230,7 @@ class LiFiBridgeProvider(BridgeProvider):
|
|
|
232
230
|
return None
|
|
233
231
|
|
|
234
232
|
from_amount = int(quote["action"]["fromAmount"])
|
|
235
|
-
from_ledger_api = self._from_ledger_api(
|
|
233
|
+
from_ledger_api = self._from_ledger_api(provider_request)
|
|
236
234
|
|
|
237
235
|
approve_tx = registry_contracts.erc20.get_approve_tx(
|
|
238
236
|
ledger_api=from_ledger_api,
|
|
@@ -242,21 +240,20 @@ class LiFiBridgeProvider(BridgeProvider):
|
|
|
242
240
|
amount=from_amount,
|
|
243
241
|
)
|
|
244
242
|
approve_tx["gas"] = 200_000 # TODO backport to ERC20 contract as default
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
approve_tx["gas"] = ceil(approve_tx["gas"] * GAS_ESTIMATE_BUFFER)
|
|
243
|
+
Provider._update_with_gas_pricing(approve_tx, from_ledger_api)
|
|
244
|
+
Provider._update_with_gas_estimate(approve_tx, from_ledger_api)
|
|
248
245
|
return approve_tx
|
|
249
246
|
|
|
250
|
-
def _get_bridge_tx(self,
|
|
247
|
+
def _get_bridge_tx(self, provider_request: ProviderRequest) -> t.Optional[t.Dict]:
|
|
251
248
|
"""Get the bridge transaction."""
|
|
252
249
|
self.logger.info(
|
|
253
|
-
f"[LI.FI
|
|
250
|
+
f"[LI.FI PROVIDER] Get bridge transaction for request {provider_request.id}."
|
|
254
251
|
)
|
|
255
252
|
|
|
256
|
-
if
|
|
253
|
+
if provider_request.params["to"]["amount"] == 0:
|
|
257
254
|
return None
|
|
258
255
|
|
|
259
|
-
quote_data =
|
|
256
|
+
quote_data = provider_request.quote_data
|
|
260
257
|
if not quote_data:
|
|
261
258
|
return None
|
|
262
259
|
|
|
@@ -274,7 +271,7 @@ class LiFiBridgeProvider(BridgeProvider):
|
|
|
274
271
|
if not transaction_request:
|
|
275
272
|
return None
|
|
276
273
|
|
|
277
|
-
from_ledger_api = self._from_ledger_api(
|
|
274
|
+
from_ledger_api = self._from_ledger_api(provider_request)
|
|
278
275
|
|
|
279
276
|
bridge_tx = {
|
|
280
277
|
"value": int(transaction_request["value"], 16),
|
|
@@ -288,36 +285,51 @@ class LiFiBridgeProvider(BridgeProvider):
|
|
|
288
285
|
transaction_request["from"]
|
|
289
286
|
),
|
|
290
287
|
}
|
|
291
|
-
|
|
292
|
-
|
|
288
|
+
Provider._update_with_gas_pricing(bridge_tx, from_ledger_api)
|
|
289
|
+
Provider._update_with_gas_estimate(bridge_tx, from_ledger_api)
|
|
293
290
|
return bridge_tx
|
|
294
291
|
|
|
295
|
-
def
|
|
292
|
+
def _get_txs(
|
|
293
|
+
self, provider_request: ProviderRequest, *args: t.Any, **kwargs: t.Any
|
|
294
|
+
) -> t.List[t.Tuple[str, t.Dict]]:
|
|
295
|
+
"""Get the sorted list of transactions to execute the quote."""
|
|
296
|
+
txs = []
|
|
297
|
+
approve_tx = self._get_approve_tx(provider_request)
|
|
298
|
+
if approve_tx:
|
|
299
|
+
txs.append(("approve_tx", approve_tx))
|
|
300
|
+
bridge_tx = self._get_bridge_tx(provider_request)
|
|
301
|
+
if bridge_tx:
|
|
302
|
+
txs.append(("bridge_tx", bridge_tx))
|
|
303
|
+
return txs
|
|
304
|
+
|
|
305
|
+
def _update_execution_status(self, provider_request: ProviderRequest) -> None:
|
|
296
306
|
"""Update the execution status."""
|
|
297
307
|
|
|
298
|
-
if
|
|
299
|
-
|
|
300
|
-
|
|
308
|
+
if provider_request.status not in (
|
|
309
|
+
ProviderRequestStatus.EXECUTION_PENDING,
|
|
310
|
+
ProviderRequestStatus.EXECUTION_UNKNOWN,
|
|
301
311
|
):
|
|
302
312
|
return
|
|
303
313
|
|
|
304
|
-
execution_data =
|
|
314
|
+
execution_data = provider_request.execution_data
|
|
305
315
|
if not execution_data:
|
|
306
316
|
raise RuntimeError(
|
|
307
|
-
f"Cannot update
|
|
317
|
+
f"Cannot update request {provider_request.id}: execution data not present."
|
|
308
318
|
)
|
|
309
319
|
|
|
310
|
-
|
|
320
|
+
from_tx_hash = execution_data.from_tx_hash
|
|
321
|
+
if not from_tx_hash:
|
|
311
322
|
return
|
|
312
323
|
|
|
324
|
+
lifi_status = LiFiTransactionStatus.UNKNOWN
|
|
313
325
|
url = "https://li.quest/v1/status"
|
|
314
326
|
headers = {"accept": "application/json"}
|
|
315
327
|
params = {
|
|
316
|
-
"txHash":
|
|
328
|
+
"txHash": from_tx_hash,
|
|
317
329
|
}
|
|
318
330
|
|
|
319
331
|
try:
|
|
320
|
-
self.logger.info(f"[LI.FI
|
|
332
|
+
self.logger.info(f"[LI.FI PROVIDER] GET {url}?{urlencode(params)}")
|
|
321
333
|
response = requests.get(url=url, headers=headers, params=params, timeout=30)
|
|
322
334
|
response_json = response.json()
|
|
323
335
|
lifi_status = response_json.get(
|
|
@@ -329,34 +341,35 @@ class LiFiBridgeProvider(BridgeProvider):
|
|
|
329
341
|
response.raise_for_status()
|
|
330
342
|
except Exception as e:
|
|
331
343
|
self.logger.error(
|
|
332
|
-
f"[LI.FI
|
|
344
|
+
f"[LI.FI PROVIDER] Failed to update status for request {provider_request.id}: {e}"
|
|
333
345
|
)
|
|
334
346
|
|
|
335
347
|
if lifi_status == LiFiTransactionStatus.DONE:
|
|
336
|
-
self.logger.info(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
348
|
+
self.logger.info(
|
|
349
|
+
f"[LI.FI PROVIDER] Execution done for {provider_request.id}."
|
|
350
|
+
)
|
|
351
|
+
from_ledger_api = self._from_ledger_api(provider_request)
|
|
352
|
+
to_ledger_api = self._to_ledger_api(provider_request)
|
|
340
353
|
to_tx_hash = response_json.get("receiving", {}).get("txHash")
|
|
341
354
|
execution_data.message = None
|
|
342
355
|
execution_data.to_tx_hash = to_tx_hash
|
|
343
|
-
execution_data.elapsed_time =
|
|
356
|
+
execution_data.elapsed_time = Provider._tx_timestamp(
|
|
344
357
|
to_tx_hash, to_ledger_api
|
|
345
|
-
) -
|
|
346
|
-
|
|
358
|
+
) - Provider._tx_timestamp(from_tx_hash, from_ledger_api)
|
|
359
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_DONE
|
|
347
360
|
elif lifi_status == LiFiTransactionStatus.FAILED:
|
|
348
|
-
|
|
361
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_FAILED
|
|
349
362
|
elif lifi_status == LiFiTransactionStatus.PENDING:
|
|
350
|
-
|
|
363
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_PENDING
|
|
351
364
|
else:
|
|
352
|
-
|
|
365
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_UNKNOWN
|
|
353
366
|
|
|
354
|
-
def _get_explorer_link(self,
|
|
367
|
+
def _get_explorer_link(self, provider_request: ProviderRequest) -> t.Optional[str]:
|
|
355
368
|
"""Get the explorer link for a transaction."""
|
|
356
|
-
if not
|
|
369
|
+
if not provider_request.execution_data:
|
|
357
370
|
return None
|
|
358
371
|
|
|
359
|
-
tx_hash =
|
|
372
|
+
tx_hash = provider_request.execution_data.from_tx_hash
|
|
360
373
|
if not tx_hash:
|
|
361
374
|
return None
|
|
362
375
|
|