@agentlayer.tech/wallet 0.1.26 → 0.1.28
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.
- package/.openclaw/extensions/agent-wallet/dist/index.js +5 -11
- package/.openclaw/extensions/agent-wallet/index.ts +5 -11
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +1 -0
- package/CHANGELOG.md +26 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +99 -84
- package/agent-wallet/agent_wallet/providers/jupiter.py +38 -14
- package/agent-wallet/agent_wallet/providers/kamino.py +6 -0
- package/agent-wallet/agent_wallet/providers/x402.py +194 -9
- package/agent-wallet/agent_wallet/transaction_policy.py +6 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +20 -0
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +230 -20
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +11 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/skills/wallet-operator/SKILL.md +1 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import base64
|
|
6
6
|
import hashlib
|
|
7
7
|
import json
|
|
8
|
+
import logging
|
|
8
9
|
from typing import Any
|
|
9
10
|
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
|
10
11
|
|
|
@@ -15,6 +16,7 @@ from agent_wallet.wallet_layer.base import AgentWalletBackend
|
|
|
15
16
|
|
|
16
17
|
CDP_BAZAAR_DISCOVERY_BASE_URL = "https://api.cdp.coinbase.com/platform/v2/x402/discovery"
|
|
17
18
|
AGENTIC_MARKET_API_BASE_URL = "https://api.agentic.market/v1"
|
|
19
|
+
X402_EXECUTE_TIMEOUT_SECONDS = 45.0
|
|
18
20
|
SOLANA_CAIP_BY_NETWORK = {
|
|
19
21
|
"mainnet": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
20
22
|
"devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
@@ -32,6 +34,7 @@ _USDC_IDENTIFIERS = {
|
|
|
32
34
|
"0x036cbd53842c5426634e7929541ec2318f3dcf7e",
|
|
33
35
|
"epjfwdd5aufqssqem2qn1xzybapc8g4wegkgkzwytdt1v",
|
|
34
36
|
}
|
|
37
|
+
log = logging.getLogger("agent_wallet.x402")
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
def _backend_chain(backend: AgentWalletBackend) -> str:
|
|
@@ -137,6 +140,13 @@ def _append_query(url: str, query: dict[str, str]) -> str:
|
|
|
137
140
|
)
|
|
138
141
|
|
|
139
142
|
|
|
143
|
+
def _request_host(url: str) -> str:
|
|
144
|
+
try:
|
|
145
|
+
return _trim(urlsplit(url).netloc).lower()
|
|
146
|
+
except Exception:
|
|
147
|
+
return ""
|
|
148
|
+
|
|
149
|
+
|
|
140
150
|
def _response_text(response: Any) -> str:
|
|
141
151
|
try:
|
|
142
152
|
text = response.text
|
|
@@ -513,6 +523,7 @@ def _build_request_metadata(
|
|
|
513
523
|
)
|
|
514
524
|
return {
|
|
515
525
|
"url": final_url,
|
|
526
|
+
"host": _request_host(final_url),
|
|
516
527
|
"method": http_method,
|
|
517
528
|
"headers": normalized_headers,
|
|
518
529
|
"query": normalized_query,
|
|
@@ -529,6 +540,7 @@ async def _send_request(
|
|
|
529
540
|
client: Any,
|
|
530
541
|
request: dict[str, Any],
|
|
531
542
|
extra_headers: dict[str, str] | None = None,
|
|
543
|
+
timeout: float | None = None,
|
|
532
544
|
) -> Any:
|
|
533
545
|
headers = dict(request["headers"])
|
|
534
546
|
if extra_headers:
|
|
@@ -539,6 +551,7 @@ async def _send_request(
|
|
|
539
551
|
headers=headers,
|
|
540
552
|
json=request["json_body"] if request["json_body"] is not None else None,
|
|
541
553
|
content=request["text_body"] if request["text_body"] is not None else None,
|
|
554
|
+
timeout=timeout,
|
|
542
555
|
)
|
|
543
556
|
|
|
544
557
|
|
|
@@ -671,6 +684,96 @@ def _require_executable_payment(
|
|
|
671
684
|
return selected
|
|
672
685
|
|
|
673
686
|
|
|
687
|
+
def _validate_payment_requirement(
|
|
688
|
+
selected: dict[str, Any] | None,
|
|
689
|
+
*,
|
|
690
|
+
backend: AgentWalletBackend,
|
|
691
|
+
request_url: str,
|
|
692
|
+
) -> dict[str, Any]:
|
|
693
|
+
if not isinstance(selected, dict):
|
|
694
|
+
raise ProviderError(
|
|
695
|
+
"x402-validate",
|
|
696
|
+
"This endpoint returned HTTP 402 but no compatible payment option was found for the active wallet.",
|
|
697
|
+
details={
|
|
698
|
+
"request_url": request_url,
|
|
699
|
+
"wallet_chain": _backend_chain(backend),
|
|
700
|
+
"wallet_network": _backend_network(backend),
|
|
701
|
+
},
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
scheme = _trim(selected.get("scheme")).lower()
|
|
705
|
+
if scheme != "exact":
|
|
706
|
+
raise ProviderError(
|
|
707
|
+
"x402-validate",
|
|
708
|
+
f"Unsupported x402 payment scheme '{scheme or 'unknown'}'. Only 'exact' is supported.",
|
|
709
|
+
details={"request_url": request_url, "selected_payment": selected},
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
if not _trim(selected.get("pay_to")):
|
|
713
|
+
raise ProviderError(
|
|
714
|
+
"x402-validate",
|
|
715
|
+
"Payment destination (payTo) is missing from the x402 requirement.",
|
|
716
|
+
details={"request_url": request_url, "selected_payment": selected},
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
compatibility = _requirement_compatibility(selected, backend)
|
|
720
|
+
if compatibility["currently_executable"]:
|
|
721
|
+
return selected
|
|
722
|
+
|
|
723
|
+
chain = _backend_chain(backend) or "unknown"
|
|
724
|
+
network = _backend_network(backend) or "unknown"
|
|
725
|
+
requirement_network = _trim(selected.get("network")) or "unknown"
|
|
726
|
+
if chain == "solana" and requirement_network not in SOLANA_CAIP_BY_NETWORK.values():
|
|
727
|
+
message = (
|
|
728
|
+
f"This endpoint requires payment on {requirement_network}, but the active wallet is Solana ({network})."
|
|
729
|
+
)
|
|
730
|
+
elif chain == "evm" and requirement_network not in EVM_CAIP_BY_NETWORK.values():
|
|
731
|
+
message = (
|
|
732
|
+
f"This endpoint requires payment on {requirement_network}, but the active wallet is EVM ({network})."
|
|
733
|
+
)
|
|
734
|
+
else:
|
|
735
|
+
message = str(compatibility["reason"])
|
|
736
|
+
|
|
737
|
+
raise ProviderError(
|
|
738
|
+
"x402-validate",
|
|
739
|
+
message,
|
|
740
|
+
details={
|
|
741
|
+
"request_url": request_url,
|
|
742
|
+
"selected_payment": selected,
|
|
743
|
+
"compatibility": compatibility,
|
|
744
|
+
},
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def _validate_request_execution_policy(
|
|
749
|
+
*,
|
|
750
|
+
request: dict[str, Any],
|
|
751
|
+
backend: AgentWalletBackend,
|
|
752
|
+
) -> None:
|
|
753
|
+
host = _trim(request.get("host")).lower()
|
|
754
|
+
if host == "x402.alchemy.com":
|
|
755
|
+
headers = request.get("headers")
|
|
756
|
+
has_auth = isinstance(headers, dict) and any(
|
|
757
|
+
str(key).strip().lower() == "authorization" and str(value).strip()
|
|
758
|
+
for key, value in headers.items()
|
|
759
|
+
)
|
|
760
|
+
if not has_auth:
|
|
761
|
+
raise ProviderError(
|
|
762
|
+
"x402-validate",
|
|
763
|
+
(
|
|
764
|
+
"Alchemy's x402 gateway needs wallet-auth headers in addition to the payment challenge. "
|
|
765
|
+
"The generic x402 tool does not mint Alchemy SIWE/SIWS auth tokens yet, so this endpoint "
|
|
766
|
+
"is not safe to execute through the generic flow."
|
|
767
|
+
),
|
|
768
|
+
details={
|
|
769
|
+
"request_url": request.get("url"),
|
|
770
|
+
"host": host,
|
|
771
|
+
"wallet_chain": _backend_chain(backend),
|
|
772
|
+
"wallet_network": _backend_network(backend),
|
|
773
|
+
"hint": "Use a dedicated Alchemy agent gateway integration or authenticated CLI flow.",
|
|
774
|
+
},
|
|
775
|
+
)
|
|
776
|
+
|
|
674
777
|
def _select_sdk_payment_requirement(
|
|
675
778
|
payment_required: Any,
|
|
676
779
|
*,
|
|
@@ -940,15 +1043,57 @@ async def _create_payment_headers(
|
|
|
940
1043
|
)
|
|
941
1044
|
|
|
942
1045
|
|
|
943
|
-
def _extract_settlement_header(response: Any) -> dict[str, Any]
|
|
1046
|
+
def _extract_settlement_header(response: Any) -> dict[str, Any]:
|
|
944
1047
|
sdk = _load_x402_sdk()
|
|
1048
|
+
settle = sdk["x402HTTPClientBase"]().get_payment_settle_response(
|
|
1049
|
+
lambda name: response.headers.get(name)
|
|
1050
|
+
)
|
|
1051
|
+
return settle.model_dump(by_alias=True, exclude_none=True)
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
def _extract_settlement_header_safe(response: Any) -> dict[str, Any] | None:
|
|
945
1055
|
try:
|
|
946
|
-
|
|
947
|
-
|
|
1056
|
+
return _extract_settlement_header(response)
|
|
1057
|
+
except Exception as exc:
|
|
1058
|
+
log.warning(
|
|
1059
|
+
"x402 settlement header parse failed",
|
|
1060
|
+
extra={
|
|
1061
|
+
"status_code": getattr(response, "status_code", None),
|
|
1062
|
+
"payment_response": response.headers.get("PAYMENT-RESPONSE")
|
|
1063
|
+
if hasattr(response, "headers")
|
|
1064
|
+
else None,
|
|
1065
|
+
"x_payment_response": response.headers.get("X-PAYMENT-RESPONSE")
|
|
1066
|
+
if hasattr(response, "headers")
|
|
1067
|
+
else None,
|
|
1068
|
+
"error_type": type(exc).__name__,
|
|
1069
|
+
"error": str(exc) or None,
|
|
1070
|
+
},
|
|
948
1071
|
)
|
|
949
|
-
except Exception:
|
|
950
1072
|
return None
|
|
951
|
-
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
def _log_x402_execute(
|
|
1076
|
+
*,
|
|
1077
|
+
request: dict[str, Any],
|
|
1078
|
+
selected_payment: dict[str, Any] | None,
|
|
1079
|
+
response: Any,
|
|
1080
|
+
settlement: dict[str, Any] | None,
|
|
1081
|
+
) -> None:
|
|
1082
|
+
log.info(
|
|
1083
|
+
"x402 execute completed",
|
|
1084
|
+
extra={
|
|
1085
|
+
"url": request.get("url"),
|
|
1086
|
+
"method": request.get("method"),
|
|
1087
|
+
"request_fingerprint": request.get("request_fingerprint"),
|
|
1088
|
+
"x402_network": selected_payment.get("network") if isinstance(selected_payment, dict) else None,
|
|
1089
|
+
"x402_asset": selected_payment.get("asset") if isinstance(selected_payment, dict) else None,
|
|
1090
|
+
"x402_amount": selected_payment.get("amount") if isinstance(selected_payment, dict) else None,
|
|
1091
|
+
"x402_pay_to": selected_payment.get("pay_to") if isinstance(selected_payment, dict) else None,
|
|
1092
|
+
"status_code": getattr(response, "status_code", None),
|
|
1093
|
+
"transaction": settlement.get("transaction") if isinstance(settlement, dict) else None,
|
|
1094
|
+
"confirmed": bool(settlement and settlement.get("success")),
|
|
1095
|
+
},
|
|
1096
|
+
)
|
|
952
1097
|
|
|
953
1098
|
|
|
954
1099
|
async def search_services(
|
|
@@ -1250,6 +1395,29 @@ async def execute_request(
|
|
|
1250
1395
|
query: dict[str, Any] | None = None,
|
|
1251
1396
|
json_body: Any | None = None,
|
|
1252
1397
|
text_body: str | None = None,
|
|
1398
|
+
) -> dict[str, Any]:
|
|
1399
|
+
executed = await pay_and_fetch(
|
|
1400
|
+
backend=backend,
|
|
1401
|
+
url=url,
|
|
1402
|
+
method=method,
|
|
1403
|
+
headers=headers,
|
|
1404
|
+
query=query,
|
|
1405
|
+
json_body=json_body,
|
|
1406
|
+
text_body=text_body,
|
|
1407
|
+
)
|
|
1408
|
+
executed["mode"] = "execute"
|
|
1409
|
+
return executed
|
|
1410
|
+
|
|
1411
|
+
|
|
1412
|
+
async def pay_and_fetch(
|
|
1413
|
+
*,
|
|
1414
|
+
backend: AgentWalletBackend,
|
|
1415
|
+
url: str,
|
|
1416
|
+
method: str = "GET",
|
|
1417
|
+
headers: dict[str, Any] | None = None,
|
|
1418
|
+
query: dict[str, Any] | None = None,
|
|
1419
|
+
json_body: Any | None = None,
|
|
1420
|
+
text_body: str | None = None,
|
|
1253
1421
|
) -> dict[str, Any]:
|
|
1254
1422
|
preview = await preview_request(
|
|
1255
1423
|
backend=backend,
|
|
@@ -1268,7 +1436,13 @@ async def execute_request(
|
|
|
1268
1436
|
executed["confirmed"] = False
|
|
1269
1437
|
return executed
|
|
1270
1438
|
|
|
1271
|
-
selected_payment =
|
|
1439
|
+
selected_payment = _validate_payment_requirement(
|
|
1440
|
+
preview.get("selected_payment")
|
|
1441
|
+
if isinstance(preview.get("selected_payment"), dict)
|
|
1442
|
+
else None,
|
|
1443
|
+
backend=backend,
|
|
1444
|
+
request_url=str(preview.get("request_url") or url),
|
|
1445
|
+
)
|
|
1272
1446
|
payment_required_header = (
|
|
1273
1447
|
dict(preview.get("response_headers") or {}).get("payment-required")
|
|
1274
1448
|
)
|
|
@@ -1283,15 +1457,26 @@ async def execute_request(
|
|
|
1283
1457
|
json_body=json_body,
|
|
1284
1458
|
text_body=text_body,
|
|
1285
1459
|
)
|
|
1460
|
+
_validate_request_execution_policy(request=request, backend=backend)
|
|
1286
1461
|
payment_headers = await _create_payment_headers(
|
|
1287
1462
|
backend=backend,
|
|
1288
1463
|
payment_required_header=payment_required_header,
|
|
1289
1464
|
selected_payment=selected_payment,
|
|
1290
1465
|
)
|
|
1291
|
-
payment_headers["Access-Control-Expose-Headers"] = "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE"
|
|
1292
1466
|
client = get_client()
|
|
1293
|
-
response = await _send_request(
|
|
1294
|
-
|
|
1467
|
+
response = await _send_request(
|
|
1468
|
+
client=client,
|
|
1469
|
+
request=request,
|
|
1470
|
+
extra_headers=payment_headers,
|
|
1471
|
+
timeout=X402_EXECUTE_TIMEOUT_SECONDS,
|
|
1472
|
+
)
|
|
1473
|
+
settlement = _extract_settlement_header_safe(response)
|
|
1474
|
+
_log_x402_execute(
|
|
1475
|
+
request=request,
|
|
1476
|
+
selected_payment=selected_payment,
|
|
1477
|
+
response=response,
|
|
1478
|
+
settlement=settlement,
|
|
1479
|
+
)
|
|
1295
1480
|
|
|
1296
1481
|
executed = dict(preview)
|
|
1297
1482
|
executed.update(
|
|
@@ -718,6 +718,7 @@ def verify_provider_kamino_lend_transaction(
|
|
|
718
718
|
market_address: str,
|
|
719
719
|
reserve_address: str,
|
|
720
720
|
action: str,
|
|
721
|
+
obligation_address: str | None = None,
|
|
721
722
|
loaded_addresses: list[str] | None = None,
|
|
722
723
|
) -> dict[str, Any]:
|
|
723
724
|
binding = _assert_basic_wallet_binding(
|
|
@@ -734,6 +735,10 @@ def verify_provider_kamino_lend_transaction(
|
|
|
734
735
|
raise WalletBackendError(
|
|
735
736
|
f"{action} transaction does not reference the expected Kamino reserve."
|
|
736
737
|
)
|
|
738
|
+
if obligation_address and obligation_address not in keys:
|
|
739
|
+
raise WalletBackendError(
|
|
740
|
+
f"{action} transaction does not reference the expected Kamino obligation."
|
|
741
|
+
)
|
|
737
742
|
program_ids = _program_ids(message, loaded_addresses)
|
|
738
743
|
unknown_program_ids = _assert_program_allowlist(
|
|
739
744
|
program_ids,
|
|
@@ -764,6 +769,7 @@ def verify_provider_kamino_lend_transaction(
|
|
|
764
769
|
"instruction_count": len(_compiled_instructions(message)),
|
|
765
770
|
"market_address": market_address,
|
|
766
771
|
"reserve_address": reserve_address,
|
|
772
|
+
"obligation_address": obligation_address,
|
|
767
773
|
"action": action,
|
|
768
774
|
"verified": True,
|
|
769
775
|
}
|
|
@@ -525,6 +525,7 @@ class AgentWalletBackend(ABC):
|
|
|
525
525
|
market: str,
|
|
526
526
|
reserve: str,
|
|
527
527
|
amount_ui: str,
|
|
528
|
+
obligation_address: str | None = None,
|
|
528
529
|
) -> dict[str, Any]:
|
|
529
530
|
raise WalletBackendError(f"{self.name} does not support Kamino deposit previews.")
|
|
530
531
|
|
|
@@ -533,6 +534,8 @@ class AgentWalletBackend(ABC):
|
|
|
533
534
|
market: str,
|
|
534
535
|
reserve: str,
|
|
535
536
|
amount_ui: str,
|
|
537
|
+
obligation_address: str | None = None,
|
|
538
|
+
approved_preview: dict[str, Any] | None = None,
|
|
536
539
|
) -> dict[str, Any]:
|
|
537
540
|
raise WalletBackendError(f"{self.name} does not support Kamino deposit preparation.")
|
|
538
541
|
|
|
@@ -541,6 +544,8 @@ class AgentWalletBackend(ABC):
|
|
|
541
544
|
market: str,
|
|
542
545
|
reserve: str,
|
|
543
546
|
amount_ui: str,
|
|
547
|
+
obligation_address: str | None = None,
|
|
548
|
+
approved_preview: dict[str, Any] | None = None,
|
|
544
549
|
) -> dict[str, Any]:
|
|
545
550
|
raise WalletBackendError(f"{self.name} does not support Kamino deposits.")
|
|
546
551
|
|
|
@@ -549,6 +554,7 @@ class AgentWalletBackend(ABC):
|
|
|
549
554
|
market: str,
|
|
550
555
|
reserve: str,
|
|
551
556
|
amount_ui: str,
|
|
557
|
+
obligation_address: str | None = None,
|
|
552
558
|
) -> dict[str, Any]:
|
|
553
559
|
raise WalletBackendError(f"{self.name} does not support Kamino withdraw previews.")
|
|
554
560
|
|
|
@@ -557,6 +563,8 @@ class AgentWalletBackend(ABC):
|
|
|
557
563
|
market: str,
|
|
558
564
|
reserve: str,
|
|
559
565
|
amount_ui: str,
|
|
566
|
+
obligation_address: str | None = None,
|
|
567
|
+
approved_preview: dict[str, Any] | None = None,
|
|
560
568
|
) -> dict[str, Any]:
|
|
561
569
|
raise WalletBackendError(f"{self.name} does not support Kamino withdraw preparation.")
|
|
562
570
|
|
|
@@ -565,6 +573,8 @@ class AgentWalletBackend(ABC):
|
|
|
565
573
|
market: str,
|
|
566
574
|
reserve: str,
|
|
567
575
|
amount_ui: str,
|
|
576
|
+
obligation_address: str | None = None,
|
|
577
|
+
approved_preview: dict[str, Any] | None = None,
|
|
568
578
|
) -> dict[str, Any]:
|
|
569
579
|
raise WalletBackendError(f"{self.name} does not support Kamino withdraws.")
|
|
570
580
|
|
|
@@ -573,6 +583,7 @@ class AgentWalletBackend(ABC):
|
|
|
573
583
|
market: str,
|
|
574
584
|
reserve: str,
|
|
575
585
|
amount_ui: str,
|
|
586
|
+
obligation_address: str | None = None,
|
|
576
587
|
) -> dict[str, Any]:
|
|
577
588
|
raise WalletBackendError(f"{self.name} does not support Kamino borrow previews.")
|
|
578
589
|
|
|
@@ -581,6 +592,8 @@ class AgentWalletBackend(ABC):
|
|
|
581
592
|
market: str,
|
|
582
593
|
reserve: str,
|
|
583
594
|
amount_ui: str,
|
|
595
|
+
obligation_address: str | None = None,
|
|
596
|
+
approved_preview: dict[str, Any] | None = None,
|
|
584
597
|
) -> dict[str, Any]:
|
|
585
598
|
raise WalletBackendError(f"{self.name} does not support Kamino borrow preparation.")
|
|
586
599
|
|
|
@@ -589,6 +602,8 @@ class AgentWalletBackend(ABC):
|
|
|
589
602
|
market: str,
|
|
590
603
|
reserve: str,
|
|
591
604
|
amount_ui: str,
|
|
605
|
+
obligation_address: str | None = None,
|
|
606
|
+
approved_preview: dict[str, Any] | None = None,
|
|
592
607
|
) -> dict[str, Any]:
|
|
593
608
|
raise WalletBackendError(f"{self.name} does not support Kamino borrows.")
|
|
594
609
|
|
|
@@ -597,6 +612,7 @@ class AgentWalletBackend(ABC):
|
|
|
597
612
|
market: str,
|
|
598
613
|
reserve: str,
|
|
599
614
|
amount_ui: str,
|
|
615
|
+
obligation_address: str | None = None,
|
|
600
616
|
) -> dict[str, Any]:
|
|
601
617
|
raise WalletBackendError(f"{self.name} does not support Kamino repay previews.")
|
|
602
618
|
|
|
@@ -605,6 +621,8 @@ class AgentWalletBackend(ABC):
|
|
|
605
621
|
market: str,
|
|
606
622
|
reserve: str,
|
|
607
623
|
amount_ui: str,
|
|
624
|
+
obligation_address: str | None = None,
|
|
625
|
+
approved_preview: dict[str, Any] | None = None,
|
|
608
626
|
) -> dict[str, Any]:
|
|
609
627
|
raise WalletBackendError(f"{self.name} does not support Kamino repay preparation.")
|
|
610
628
|
|
|
@@ -613,6 +631,8 @@ class AgentWalletBackend(ABC):
|
|
|
613
631
|
market: str,
|
|
614
632
|
reserve: str,
|
|
615
633
|
amount_ui: str,
|
|
634
|
+
obligation_address: str | None = None,
|
|
635
|
+
approved_preview: dict[str, Any] | None = None,
|
|
616
636
|
) -> dict[str, Any]:
|
|
617
637
|
raise WalletBackendError(f"{self.name} does not support Kamino repays.")
|
|
618
638
|
|