@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
|
@@ -956,7 +956,7 @@ const walletSessionToolDefinitions = [
|
|
|
956
956
|
{
|
|
957
957
|
name: "x402_pay_request",
|
|
958
958
|
description:
|
|
959
|
-
|
|
959
|
+
"Pay for and call an x402 endpoint using the active wallet backend. The tool probes the endpoint, validates compatibility, signs the payment, and returns the service response in one call.",
|
|
960
960
|
parameters: {
|
|
961
961
|
type: "object",
|
|
962
962
|
properties: {
|
|
@@ -966,18 +966,9 @@ const walletSessionToolDefinitions = [
|
|
|
966
966
|
query: { type: "object", additionalProperties: true },
|
|
967
967
|
json_body: {},
|
|
968
968
|
text_body: { type: "string" },
|
|
969
|
-
mode: {
|
|
970
|
-
type: "string",
|
|
971
|
-
enum: ["prepare", "execute"],
|
|
972
|
-
description: "prepare validates the payment plan; execute sends the paid retry.",
|
|
973
|
-
},
|
|
974
969
|
purpose: { type: "string" },
|
|
975
|
-
user_intent: {
|
|
976
|
-
type: "boolean",
|
|
977
|
-
description: "Must be true for prepare mode.",
|
|
978
|
-
},
|
|
979
970
|
},
|
|
980
|
-
required: ["url", "
|
|
971
|
+
required: ["url", "purpose"],
|
|
981
972
|
additionalProperties: false,
|
|
982
973
|
},
|
|
983
974
|
},
|
|
@@ -1535,6 +1526,7 @@ const solanaToolDefinitions = [
|
|
|
1535
1526
|
market: { type: "string" },
|
|
1536
1527
|
reserve: { type: "string" },
|
|
1537
1528
|
amount_ui: { type: "string" },
|
|
1529
|
+
obligation_address: { type: "string" },
|
|
1538
1530
|
mode: { type: "string", enum: ["preview", "prepare", "execute"] },
|
|
1539
1531
|
purpose: { type: "string" },
|
|
1540
1532
|
user_intent: { type: "boolean" },
|
|
@@ -1553,6 +1545,7 @@ const solanaToolDefinitions = [
|
|
|
1553
1545
|
market: { type: "string" },
|
|
1554
1546
|
reserve: { type: "string" },
|
|
1555
1547
|
amount_ui: { type: "string" },
|
|
1548
|
+
obligation_address: { type: "string" },
|
|
1556
1549
|
mode: { type: "string", enum: ["preview", "prepare", "execute"] },
|
|
1557
1550
|
purpose: { type: "string" },
|
|
1558
1551
|
user_intent: { type: "boolean" },
|
|
@@ -1571,6 +1564,7 @@ const solanaToolDefinitions = [
|
|
|
1571
1564
|
market: { type: "string" },
|
|
1572
1565
|
reserve: { type: "string" },
|
|
1573
1566
|
amount_ui: { type: "string" },
|
|
1567
|
+
obligation_address: { type: "string" },
|
|
1574
1568
|
mode: { type: "string", enum: ["preview", "prepare", "execute"] },
|
|
1575
1569
|
purpose: { type: "string" },
|
|
1576
1570
|
user_intent: { type: "boolean" },
|
|
@@ -956,7 +956,7 @@ const walletSessionToolDefinitions = [
|
|
|
956
956
|
{
|
|
957
957
|
name: "x402_pay_request",
|
|
958
958
|
description:
|
|
959
|
-
|
|
959
|
+
"Pay for and call an x402 endpoint using the active wallet backend. The tool probes the endpoint, validates compatibility, signs the payment, and returns the service response in one call.",
|
|
960
960
|
parameters: {
|
|
961
961
|
type: "object",
|
|
962
962
|
properties: {
|
|
@@ -966,18 +966,9 @@ const walletSessionToolDefinitions = [
|
|
|
966
966
|
query: { type: "object", additionalProperties: true },
|
|
967
967
|
json_body: {},
|
|
968
968
|
text_body: { type: "string" },
|
|
969
|
-
mode: {
|
|
970
|
-
type: "string",
|
|
971
|
-
enum: ["prepare", "execute"],
|
|
972
|
-
description: "prepare validates the payment plan; execute sends the paid retry.",
|
|
973
|
-
},
|
|
974
969
|
purpose: { type: "string" },
|
|
975
|
-
user_intent: {
|
|
976
|
-
type: "boolean",
|
|
977
|
-
description: "Must be true for prepare mode.",
|
|
978
|
-
},
|
|
979
970
|
},
|
|
980
|
-
required: ["url", "
|
|
971
|
+
required: ["url", "purpose"],
|
|
981
972
|
additionalProperties: false,
|
|
982
973
|
},
|
|
983
974
|
},
|
|
@@ -1535,6 +1526,7 @@ const solanaToolDefinitions = [
|
|
|
1535
1526
|
market: { type: "string" },
|
|
1536
1527
|
reserve: { type: "string" },
|
|
1537
1528
|
amount_ui: { type: "string" },
|
|
1529
|
+
obligation_address: { type: "string" },
|
|
1538
1530
|
mode: { type: "string", enum: ["preview", "prepare", "execute"] },
|
|
1539
1531
|
purpose: { type: "string" },
|
|
1540
1532
|
user_intent: { type: "boolean" },
|
|
@@ -1553,6 +1545,7 @@ const solanaToolDefinitions = [
|
|
|
1553
1545
|
market: { type: "string" },
|
|
1554
1546
|
reserve: { type: "string" },
|
|
1555
1547
|
amount_ui: { type: "string" },
|
|
1548
|
+
obligation_address: { type: "string" },
|
|
1556
1549
|
mode: { type: "string", enum: ["preview", "prepare", "execute"] },
|
|
1557
1550
|
purpose: { type: "string" },
|
|
1558
1551
|
user_intent: { type: "boolean" },
|
|
@@ -1571,6 +1564,7 @@ const solanaToolDefinitions = [
|
|
|
1571
1564
|
market: { type: "string" },
|
|
1572
1565
|
reserve: { type: "string" },
|
|
1573
1566
|
amount_ui: { type: "string" },
|
|
1567
|
+
obligation_address: { type: "string" },
|
|
1574
1568
|
mode: { type: "string", enum: ["preview", "prepare", "execute"] },
|
|
1575
1569
|
purpose: { type: "string" },
|
|
1576
1570
|
user_intent: { type: "boolean" },
|
|
@@ -13,6 +13,7 @@ Safety rules:
|
|
|
13
13
|
- For transfers, native staking, swaps, Aave writes, Jupiter Earn writes, and Kamino writes, use `preview` before `prepare` or `execute`.
|
|
14
14
|
- For Solana Jupiter swaps through `swap_solana_tokens`, prefer `intent_preview` then `intent_execute` after explicit chat confirmation. The user confirms risk limits; the backend refreshes the quote and only executes inside those limits.
|
|
15
15
|
- Solana swap intent defaults to at least 300 bps (3%) slippage, 120 seconds validity, and 3 fresh execution attempts. The backend computes the approved minimum output from the indicative output and slippage, clamps hand-tightened minimums to that floor, then executes through Jupiter Swap API V2 `/order` + `/execute` when available.
|
|
16
|
+
- Metis `/swap` fallback builds use Jupiter dynamic slippage and a bounded `veryHigh` priority fee instead of the old `"auto"` priority mode.
|
|
16
17
|
- Do not use legacy `execute` for Solana Jupiter swaps in OpenClaw. Exact quote-bound approval is too fragile for active markets and will be rejected by the bridge.
|
|
17
18
|
- For `swap_solana_privately`, use `preview` and then `execute` after explicit user approval. Do not use `prepare` for this tool.
|
|
18
19
|
- Use `prepare` only when the user clearly intends to produce an execution plan.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v0.1.28 - 2026-05-28
|
|
6
|
+
|
|
7
|
+
- Simplified `x402_pay_request` into a single-shot paid execution flow while
|
|
8
|
+
keeping `x402_preview_request` as an optional research tool.
|
|
9
|
+
- Hardened x402 execution with explicit payment requirement validation, longer
|
|
10
|
+
paid-request timeouts, structured settlement logging, and safer settlement
|
|
11
|
+
header parsing.
|
|
12
|
+
- Fixed Base x402 EVM signing by normalizing typed-data byte fields before
|
|
13
|
+
sending them through the local WDK signer bridge.
|
|
14
|
+
- Reduced x402 preview confusion in OpenClaw by exposing a payment summary
|
|
15
|
+
without approval-token style confirmation semantics.
|
|
16
|
+
- Added an early safety guard for `x402.alchemy.com` so unsupported wallet-auth
|
|
17
|
+
flows fail before spending funds.
|
|
18
|
+
|
|
19
|
+
## v0.1.27 - 2026-05-27
|
|
20
|
+
|
|
21
|
+
- Improved Solana swap fallback landing by enabling Jupiter dynamic slippage
|
|
22
|
+
and bounded `veryHigh` priority fees on the Metis `/swap` fallback path.
|
|
23
|
+
- Hardened Kamino transaction execution with local simulation before send,
|
|
24
|
+
Kamino-specific build timeouts, and longer confirmation polling on mainnet.
|
|
25
|
+
- Reused approved Kamino preview payloads during execute so OpenClaw no longer
|
|
26
|
+
needs to rebuild the same write path just to satisfy approval binding.
|
|
27
|
+
- Added Kamino obligation pinning for `withdraw`, `borrow`, and `repay`, so
|
|
28
|
+
preview can require an explicit `obligation_address` and execute verifies the
|
|
29
|
+
built transaction references the selected obligation before signing.
|
|
30
|
+
|
|
5
31
|
## v0.1.26 - 2026-05-26
|
|
6
32
|
|
|
7
33
|
- Reworked Solana Jupiter swaps to prefer intent approvals, so OpenClaw confirms
|
|
@@ -245,8 +245,9 @@ class OpenClawWalletAdapter:
|
|
|
245
245
|
AgentToolSpec(
|
|
246
246
|
name="x402_pay_request",
|
|
247
247
|
description=(
|
|
248
|
-
"
|
|
249
|
-
"
|
|
248
|
+
"Pay for and call an x402 endpoint using the active wallet backend. "
|
|
249
|
+
"The tool probes the endpoint, validates compatibility, signs the payment, "
|
|
250
|
+
"and returns the service response in one call."
|
|
250
251
|
),
|
|
251
252
|
input_schema={
|
|
252
253
|
"type": "object",
|
|
@@ -257,26 +258,13 @@ class OpenClawWalletAdapter:
|
|
|
257
258
|
"query": {"type": "object", "additionalProperties": True},
|
|
258
259
|
"json_body": {},
|
|
259
260
|
"text_body": {"type": "string"},
|
|
260
|
-
"mode": {
|
|
261
|
-
"type": "string",
|
|
262
|
-
"enum": ["prepare", "execute"],
|
|
263
|
-
"description": "prepare validates the payment plan; execute sends the paid retry.",
|
|
264
|
-
},
|
|
265
261
|
"purpose": {"type": "string"},
|
|
266
|
-
"user_intent": {
|
|
267
|
-
"type": "boolean",
|
|
268
|
-
"description": "Must be true for prepare mode.",
|
|
269
|
-
},
|
|
270
|
-
"approval_token": {
|
|
271
|
-
"type": "string",
|
|
272
|
-
"description": "Required for execute mode and must be issued against the exact x402 payment summary.",
|
|
273
|
-
},
|
|
274
262
|
},
|
|
275
|
-
"required": ["url", "
|
|
263
|
+
"required": ["url", "purpose"],
|
|
276
264
|
"additionalProperties": False,
|
|
277
265
|
},
|
|
278
266
|
read_only=False,
|
|
279
|
-
requires_explicit_user_intent=
|
|
267
|
+
requires_explicit_user_intent=False,
|
|
280
268
|
risk_level="high",
|
|
281
269
|
),
|
|
282
270
|
]
|
|
@@ -760,6 +748,7 @@ class OpenClawWalletAdapter:
|
|
|
760
748
|
"owner",
|
|
761
749
|
"authority",
|
|
762
750
|
"address",
|
|
751
|
+
"obligation_address",
|
|
763
752
|
"market",
|
|
764
753
|
"reserve",
|
|
765
754
|
"amount_native",
|
|
@@ -871,6 +860,42 @@ class OpenClawWalletAdapter:
|
|
|
871
860
|
)
|
|
872
861
|
return annotated
|
|
873
862
|
|
|
863
|
+
def _annotate_x402_payload(
|
|
864
|
+
self,
|
|
865
|
+
payload: dict[str, Any],
|
|
866
|
+
*,
|
|
867
|
+
mode: str,
|
|
868
|
+
) -> dict[str, Any]:
|
|
869
|
+
if not payload.get("payment_required"):
|
|
870
|
+
return dict(payload)
|
|
871
|
+
annotated = self._annotate_sensitive_payload(
|
|
872
|
+
payload,
|
|
873
|
+
action_label="x402 paid request",
|
|
874
|
+
mode=mode,
|
|
875
|
+
)
|
|
876
|
+
summary = dict(annotated.get("confirmation_summary") or {})
|
|
877
|
+
if summary:
|
|
878
|
+
annotated["payment_summary"] = summary
|
|
879
|
+
requirements = dict(annotated.get("confirmation_requirements") or {})
|
|
880
|
+
requirements["prepare_requires_user_intent"] = False
|
|
881
|
+
requirements["execute_requires_approval_token"] = False
|
|
882
|
+
requirements["execute_requires_mainnet_confirmed_in_token"] = False
|
|
883
|
+
annotated.pop("approval_hint", None)
|
|
884
|
+
if mode == "preview":
|
|
885
|
+
annotated.pop("confirmation_summary", None)
|
|
886
|
+
annotated.pop("confirmation_requirements", None)
|
|
887
|
+
if annotated.get("is_mainnet"):
|
|
888
|
+
annotated["preview_note"] = (
|
|
889
|
+
"This is a paid mainnet endpoint preview only. Review the service URL, network, asset, amount, and payment destination before calling x402_pay_request."
|
|
890
|
+
)
|
|
891
|
+
return annotated
|
|
892
|
+
annotated["confirmation_requirements"] = requirements
|
|
893
|
+
if annotated.get("is_mainnet"):
|
|
894
|
+
annotated["mainnet_warning"] = (
|
|
895
|
+
"Mainnet x402 payment. Confirm the service URL, network, asset, amount, and payment destination before paying."
|
|
896
|
+
)
|
|
897
|
+
return annotated
|
|
898
|
+
|
|
874
899
|
def list_tools(self) -> list[AgentToolSpec]:
|
|
875
900
|
"""Return wallet tools suitable for agent registration."""
|
|
876
901
|
capabilities = self.backend.get_capabilities()
|
|
@@ -2969,6 +2994,10 @@ class OpenClawWalletAdapter:
|
|
|
2969
2994
|
"type": "string",
|
|
2970
2995
|
"description": "Decimal token amount to withdraw, as a string.",
|
|
2971
2996
|
},
|
|
2997
|
+
"obligation_address": {
|
|
2998
|
+
"type": "string",
|
|
2999
|
+
"description": "Optional Kamino obligation address. Required when preview shows multiple matching obligations.",
|
|
3000
|
+
},
|
|
2972
3001
|
"mode": {
|
|
2973
3002
|
"type": "string",
|
|
2974
3003
|
"enum": ["preview", "prepare", "execute"],
|
|
@@ -3003,6 +3032,10 @@ class OpenClawWalletAdapter:
|
|
|
3003
3032
|
"type": "string",
|
|
3004
3033
|
"description": "Decimal token amount to borrow, as a string.",
|
|
3005
3034
|
},
|
|
3035
|
+
"obligation_address": {
|
|
3036
|
+
"type": "string",
|
|
3037
|
+
"description": "Optional Kamino obligation address. Required when preview shows multiple obligations in the selected market.",
|
|
3038
|
+
},
|
|
3006
3039
|
"mode": {
|
|
3007
3040
|
"type": "string",
|
|
3008
3041
|
"enum": ["preview", "prepare", "execute"],
|
|
@@ -3037,6 +3070,10 @@ class OpenClawWalletAdapter:
|
|
|
3037
3070
|
"type": "string",
|
|
3038
3071
|
"description": "Decimal token amount to repay, as a string.",
|
|
3039
3072
|
},
|
|
3073
|
+
"obligation_address": {
|
|
3074
|
+
"type": "string",
|
|
3075
|
+
"description": "Optional Kamino obligation address. Required when preview shows multiple matching debt obligations.",
|
|
3076
|
+
},
|
|
3040
3077
|
"mode": {
|
|
3041
3078
|
"type": "string",
|
|
3042
3079
|
"enum": ["preview", "prepare", "execute"],
|
|
@@ -3296,14 +3333,7 @@ class OpenClawWalletAdapter:
|
|
|
3296
3333
|
text_body=text_body,
|
|
3297
3334
|
)
|
|
3298
3335
|
if data.get("payment_required"):
|
|
3299
|
-
data = self.
|
|
3300
|
-
data,
|
|
3301
|
-
action_label="x402 paid request",
|
|
3302
|
-
mode="preview",
|
|
3303
|
-
)
|
|
3304
|
-
approval_hint = dict(data.get("approval_hint") or {})
|
|
3305
|
-
approval_hint["tool_name"] = "x402_pay_request"
|
|
3306
|
-
data["approval_hint"] = approval_hint
|
|
3336
|
+
data = self._annotate_x402_payload(data, mode="preview")
|
|
3307
3337
|
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
3308
3338
|
|
|
3309
3339
|
if tool_name == "x402_pay_request":
|
|
@@ -3313,10 +3343,7 @@ class OpenClawWalletAdapter:
|
|
|
3313
3343
|
query = args.get("query")
|
|
3314
3344
|
json_body = args.get("json_body")
|
|
3315
3345
|
text_body = args.get("text_body")
|
|
3316
|
-
mode = str(args.get("mode") or "").strip().lower()
|
|
3317
3346
|
purpose = args.get("purpose")
|
|
3318
|
-
user_intent = args.get("user_intent")
|
|
3319
|
-
approval_token = args.get("approval_token")
|
|
3320
3347
|
if not isinstance(url, str) or not url.strip():
|
|
3321
3348
|
raise WalletBackendError("url is required.")
|
|
3322
3349
|
if method is not None and not isinstance(method, str):
|
|
@@ -3327,51 +3354,9 @@ class OpenClawWalletAdapter:
|
|
|
3327
3354
|
raise WalletBackendError("query must be an object when provided.")
|
|
3328
3355
|
if text_body is not None and not isinstance(text_body, str):
|
|
3329
3356
|
raise WalletBackendError("text_body must be a string when provided.")
|
|
3330
|
-
if mode not in {"prepare", "execute"}:
|
|
3331
|
-
raise WalletBackendError("mode must be 'prepare' or 'execute'.")
|
|
3332
3357
|
if not isinstance(purpose, str) or not purpose.strip():
|
|
3333
3358
|
raise WalletBackendError("purpose is required.")
|
|
3334
|
-
|
|
3335
|
-
self._require_prepare_intent(user_intent)
|
|
3336
|
-
data = await x402.prepare_request(
|
|
3337
|
-
backend=active_backend,
|
|
3338
|
-
url=url.strip(),
|
|
3339
|
-
method=method,
|
|
3340
|
-
headers=headers,
|
|
3341
|
-
query=query,
|
|
3342
|
-
json_body=json_body,
|
|
3343
|
-
text_body=text_body,
|
|
3344
|
-
)
|
|
3345
|
-
data["purpose"] = purpose.strip()
|
|
3346
|
-
data = self._annotate_sensitive_payload(
|
|
3347
|
-
data,
|
|
3348
|
-
action_label="x402 paid request",
|
|
3349
|
-
mode="prepare",
|
|
3350
|
-
)
|
|
3351
|
-
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
3352
|
-
preview = await x402.prepare_request(
|
|
3353
|
-
backend=active_backend,
|
|
3354
|
-
url=url.strip(),
|
|
3355
|
-
method=method,
|
|
3356
|
-
headers=headers,
|
|
3357
|
-
query=query,
|
|
3358
|
-
json_body=json_body,
|
|
3359
|
-
text_body=text_body,
|
|
3360
|
-
)
|
|
3361
|
-
preview["purpose"] = purpose.strip()
|
|
3362
|
-
preview = self._annotate_sensitive_payload(
|
|
3363
|
-
preview,
|
|
3364
|
-
action_label="x402 paid request",
|
|
3365
|
-
mode="execute",
|
|
3366
|
-
)
|
|
3367
|
-
self._require_execute_approval(
|
|
3368
|
-
approval_token=approval_token,
|
|
3369
|
-
tool_name=tool_name,
|
|
3370
|
-
summary=preview["confirmation_summary"],
|
|
3371
|
-
action_label="x402 paid request",
|
|
3372
|
-
backend=active_backend,
|
|
3373
|
-
)
|
|
3374
|
-
data = await x402.execute_request(
|
|
3359
|
+
data = await x402.pay_and_fetch(
|
|
3375
3360
|
backend=active_backend,
|
|
3376
3361
|
url=url.strip(),
|
|
3377
3362
|
method=method,
|
|
@@ -3381,11 +3366,7 @@ class OpenClawWalletAdapter:
|
|
|
3381
3366
|
text_body=text_body,
|
|
3382
3367
|
)
|
|
3383
3368
|
data["purpose"] = purpose.strip()
|
|
3384
|
-
data = self.
|
|
3385
|
-
data,
|
|
3386
|
-
action_label="x402 paid request",
|
|
3387
|
-
mode="execute",
|
|
3388
|
-
)
|
|
3369
|
+
data = self._annotate_x402_payload(data, mode="execute")
|
|
3389
3370
|
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
3390
3371
|
|
|
3391
3372
|
if tool_name == "get_wallet_capabilities":
|
|
@@ -5003,6 +4984,7 @@ class OpenClawWalletAdapter:
|
|
|
5003
4984
|
market = args.get("market")
|
|
5004
4985
|
reserve = args.get("reserve")
|
|
5005
4986
|
amount_ui = args.get("amount_ui")
|
|
4987
|
+
obligation_address = args.get("obligation_address")
|
|
5006
4988
|
mode = args.get("mode")
|
|
5007
4989
|
purpose = args.get("purpose")
|
|
5008
4990
|
user_intent = args.get("user_intent", False)
|
|
@@ -5014,6 +4996,8 @@ class OpenClawWalletAdapter:
|
|
|
5014
4996
|
raise WalletBackendError("reserve is required.")
|
|
5015
4997
|
if not isinstance(amount_ui, str) or not amount_ui.strip():
|
|
5016
4998
|
raise WalletBackendError("amount_ui is required.")
|
|
4999
|
+
if obligation_address is not None and not isinstance(obligation_address, str):
|
|
5000
|
+
raise WalletBackendError("obligation_address must be a string when provided.")
|
|
5017
5001
|
if mode not in {"preview", "prepare", "execute"}:
|
|
5018
5002
|
raise WalletBackendError("mode must be 'preview', 'prepare' or 'execute'.")
|
|
5019
5003
|
if not isinstance(purpose, str) or not purpose.strip():
|
|
@@ -5046,6 +5030,7 @@ class OpenClawWalletAdapter:
|
|
|
5046
5030
|
market=market.strip(),
|
|
5047
5031
|
reserve=reserve.strip(),
|
|
5048
5032
|
amount_ui=amount_ui.strip(),
|
|
5033
|
+
obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
|
|
5049
5034
|
)
|
|
5050
5035
|
return AgentToolResult(
|
|
5051
5036
|
tool=tool_name,
|
|
@@ -5063,7 +5048,12 @@ class OpenClawWalletAdapter:
|
|
|
5063
5048
|
market=market.strip(),
|
|
5064
5049
|
reserve=reserve.strip(),
|
|
5065
5050
|
amount_ui=amount_ui.strip(),
|
|
5051
|
+
obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
|
|
5066
5052
|
)
|
|
5053
|
+
if bool(preview.get("requires_obligation_address")):
|
|
5054
|
+
raise WalletBackendError(
|
|
5055
|
+
f"{action_label} requires obligation_address when multiple Kamino obligations match the selected position."
|
|
5056
|
+
)
|
|
5067
5057
|
return AgentToolResult(
|
|
5068
5058
|
tool=tool_name,
|
|
5069
5059
|
ok=True,
|
|
@@ -5077,24 +5067,49 @@ class OpenClawWalletAdapter:
|
|
|
5077
5067
|
),
|
|
5078
5068
|
)
|
|
5079
5069
|
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5070
|
+
approved_preview = args.get("_approved_preview")
|
|
5071
|
+
execute_preview = None
|
|
5072
|
+
approval_payload = inspect_approval_token(
|
|
5073
|
+
approval_token,
|
|
5074
|
+
tool_name=tool_name,
|
|
5075
|
+
network=str(getattr(self.backend, "network", "unknown")),
|
|
5076
|
+
require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
|
|
5084
5077
|
)
|
|
5078
|
+
approval_summary = approval_payload.get("binding", {}).get("summary")
|
|
5079
|
+
if not isinstance(approval_summary, dict):
|
|
5080
|
+
raise WalletBackendError(
|
|
5081
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
5082
|
+
)
|
|
5083
|
+
approval_summary_copy = dict(approval_summary)
|
|
5084
|
+
if isinstance(approval_summary_copy.get("_preview_digest"), str):
|
|
5085
|
+
if not isinstance(approved_preview, dict):
|
|
5086
|
+
raise WalletBackendError(
|
|
5087
|
+
f"Approved {action_label} preview payload is required for execute mode. Generate a new preview and approval before execute."
|
|
5088
|
+
)
|
|
5089
|
+
if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
|
|
5090
|
+
raise WalletBackendError(
|
|
5091
|
+
"approved preview payload does not match the approval token. Generate a new preview and approval before execute."
|
|
5092
|
+
)
|
|
5093
|
+
execute_preview = dict(approved_preview)
|
|
5094
|
+
else:
|
|
5095
|
+
execute_preview = await preview_method(
|
|
5096
|
+
market=market.strip(),
|
|
5097
|
+
reserve=reserve.strip(),
|
|
5098
|
+
amount_ui=amount_ui.strip(),
|
|
5099
|
+
obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
|
|
5100
|
+
)
|
|
5085
5101
|
self._require_execute_approval(
|
|
5086
5102
|
approval_token=approval_token,
|
|
5087
5103
|
tool_name=tool_name,
|
|
5088
|
-
summary=
|
|
5089
|
-
action_label=action_label,
|
|
5090
|
-
payload=execute_preview,
|
|
5091
|
-
),
|
|
5104
|
+
summary=approval_summary_copy,
|
|
5092
5105
|
action_label=action_label,
|
|
5093
5106
|
)
|
|
5094
5107
|
result = await execute_method(
|
|
5095
5108
|
market=market.strip(),
|
|
5096
5109
|
reserve=reserve.strip(),
|
|
5097
5110
|
amount_ui=amount_ui.strip(),
|
|
5111
|
+
obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
|
|
5112
|
+
approved_preview=execute_preview,
|
|
5098
5113
|
)
|
|
5099
5114
|
return AgentToolResult(
|
|
5100
5115
|
tool=tool_name,
|
|
@@ -10,6 +10,8 @@ from agent_wallet.config import settings
|
|
|
10
10
|
from agent_wallet.exceptions import ProviderError
|
|
11
11
|
from agent_wallet.http_client import get_client
|
|
12
12
|
|
|
13
|
+
JUPITER_SWAP_FALLBACK_PRIORITY_MAX_LAMPORTS = 2_000_000
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
def _headers() -> dict[str, str]:
|
|
15
17
|
headers = {"Accept": "application/json"}
|
|
@@ -91,6 +93,32 @@ def _direct_jupiter_enabled() -> bool:
|
|
|
91
93
|
return bool(settings.jupiter_api_key.strip())
|
|
92
94
|
|
|
93
95
|
|
|
96
|
+
def _swap_fallback_prioritization_fee() -> dict[str, Any]:
|
|
97
|
+
return {
|
|
98
|
+
"priorityLevelWithMaxLamports": {
|
|
99
|
+
"priorityLevel": "veryHigh",
|
|
100
|
+
"maxLamports": JUPITER_SWAP_FALLBACK_PRIORITY_MAX_LAMPORTS,
|
|
101
|
+
"global": False,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _swap_fallback_build_body(
|
|
107
|
+
*,
|
|
108
|
+
user_public_key: str,
|
|
109
|
+
quote_response: dict[str, Any],
|
|
110
|
+
wrap_and_unwrap_sol: bool,
|
|
111
|
+
) -> dict[str, Any]:
|
|
112
|
+
return {
|
|
113
|
+
"userPublicKey": user_public_key,
|
|
114
|
+
"quoteResponse": quote_response,
|
|
115
|
+
"wrapAndUnwrapSol": wrap_and_unwrap_sol,
|
|
116
|
+
"dynamicComputeUnitLimit": True,
|
|
117
|
+
"dynamicSlippage": True,
|
|
118
|
+
"prioritizationFeeLamports": _swap_fallback_prioritization_fee(),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
94
122
|
def _swap_v2_base_url() -> str:
|
|
95
123
|
return os.getenv(
|
|
96
124
|
"JUPITER_SWAP_V2_API_BASE_URL",
|
|
@@ -456,13 +484,11 @@ async def _build_swap_direct(
|
|
|
456
484
|
) -> dict[str, Any]:
|
|
457
485
|
"""Build a swap transaction directly via Jupiter API."""
|
|
458
486
|
client = get_client()
|
|
459
|
-
body =
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
"prioritizationFeeLamports": "auto",
|
|
465
|
-
}
|
|
487
|
+
body = _swap_fallback_build_body(
|
|
488
|
+
user_public_key=user_public_key,
|
|
489
|
+
quote_response=quote_response,
|
|
490
|
+
wrap_and_unwrap_sol=wrap_and_unwrap_sol,
|
|
491
|
+
)
|
|
466
492
|
response = await client.post(
|
|
467
493
|
f"{settings.jupiter_api_base_url.rstrip('/')}/swap",
|
|
468
494
|
json=body,
|
|
@@ -483,13 +509,11 @@ async def _build_swap_via_gateway(
|
|
|
483
509
|
wrap_and_unwrap_sol: bool = True,
|
|
484
510
|
) -> dict[str, Any]:
|
|
485
511
|
"""Build a swap transaction via provider gateway (uses API key)."""
|
|
486
|
-
body =
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
"prioritizationFeeLamports": "auto",
|
|
492
|
-
}
|
|
512
|
+
body = _swap_fallback_build_body(
|
|
513
|
+
user_public_key=user_public_key,
|
|
514
|
+
quote_response=quote_response,
|
|
515
|
+
wrap_and_unwrap_sol=wrap_and_unwrap_sol,
|
|
516
|
+
)
|
|
493
517
|
status_code, payload = await _gateway_post("swap", body=body)
|
|
494
518
|
if status_code != 200:
|
|
495
519
|
error_msg = payload if isinstance(payload, str) else json.dumps(payload)
|
|
@@ -8,6 +8,8 @@ from agent_wallet.config import settings
|
|
|
8
8
|
from agent_wallet.exceptions import ProviderError
|
|
9
9
|
from agent_wallet.http_client import get_client
|
|
10
10
|
|
|
11
|
+
KAMINO_BUILD_TIMEOUT_SECONDS = 20.0
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
def _normalized_api_base() -> str:
|
|
13
15
|
return settings.kamino_api_base_url.rstrip("/")
|
|
@@ -140,6 +142,7 @@ async def build_lend_deposit_transaction(
|
|
|
140
142
|
"reserve": reserve,
|
|
141
143
|
"amount": amount_ui,
|
|
142
144
|
},
|
|
145
|
+
timeout=KAMINO_BUILD_TIMEOUT_SECONDS,
|
|
143
146
|
)
|
|
144
147
|
if response.status_code != 200:
|
|
145
148
|
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
@@ -163,6 +166,7 @@ async def build_lend_withdraw_transaction(
|
|
|
163
166
|
"reserve": reserve,
|
|
164
167
|
"amount": amount_ui,
|
|
165
168
|
},
|
|
169
|
+
timeout=KAMINO_BUILD_TIMEOUT_SECONDS,
|
|
166
170
|
)
|
|
167
171
|
if response.status_code != 200:
|
|
168
172
|
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
@@ -186,6 +190,7 @@ async def build_lend_borrow_transaction(
|
|
|
186
190
|
"reserve": reserve,
|
|
187
191
|
"amount": amount_ui,
|
|
188
192
|
},
|
|
193
|
+
timeout=KAMINO_BUILD_TIMEOUT_SECONDS,
|
|
189
194
|
)
|
|
190
195
|
if response.status_code != 200:
|
|
191
196
|
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
@@ -209,6 +214,7 @@ async def build_lend_repay_transaction(
|
|
|
209
214
|
"reserve": reserve,
|
|
210
215
|
"amount": amount_ui,
|
|
211
216
|
},
|
|
217
|
+
timeout=KAMINO_BUILD_TIMEOUT_SECONDS,
|
|
212
218
|
)
|
|
213
219
|
if response.status_code != 200:
|
|
214
220
|
raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
|