@agentlayer.tech/wallet 0.1.47 → 0.1.49
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/index.ts +45 -0
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +4 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/VERSION +1 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/autonomous_permissions.py +145 -0
- package/agent-wallet/agent_wallet/autonomous_policy.py +305 -0
- package/agent-wallet/agent_wallet/autonomous_session.py +236 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +682 -43
- package/agent-wallet/agent_wallet/openclaw_cli.py +36 -0
- package/agent-wallet/agent_wallet/spending_limits.py +21 -3
- package/agent-wallet/agent_wallet/wallet_layer/base.py +110 -0
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/README.md +5 -0
- package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-approve.md +24 -0
- package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-revoke.md +16 -0
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/server.py +18 -0
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/package.json +1 -1
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/package.json +1 -1
|
@@ -4,9 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import hashlib
|
|
6
6
|
import json
|
|
7
|
+
import time
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
from agent_wallet.approval import inspect_approval_token, verify_approval_token
|
|
11
|
+
from agent_wallet.autonomous_policy import OperationRequest
|
|
10
12
|
from agent_wallet.exceptions import ProviderError
|
|
11
13
|
from agent_wallet.models import AgentToolResult, AgentToolSpec
|
|
12
14
|
from agent_wallet.providers import x402
|
|
@@ -278,6 +280,113 @@ class OpenClawWalletAdapter:
|
|
|
278
280
|
),
|
|
279
281
|
]
|
|
280
282
|
|
|
283
|
+
@staticmethod
|
|
284
|
+
def _autonomous_signals(summary: dict[str, Any]) -> tuple[str | None, int | None]:
|
|
285
|
+
"""Best-effort extraction of (recipient, smallest-unit spend) from a summary.
|
|
286
|
+
|
|
287
|
+
Only raw/base-unit amount fields are trusted for spend accounting; a
|
|
288
|
+
UI-denominated amount is ignored so caps are never applied at the wrong
|
|
289
|
+
scale. When no raw amount is present the spend is reported as ``None``
|
|
290
|
+
(unknown), which the session layer treats as fail-closed when caps are
|
|
291
|
+
configured.
|
|
292
|
+
"""
|
|
293
|
+
recipient: str | None = None
|
|
294
|
+
for key in ("to_address", "to", "recipient", "destination", "destination_address", "spender"):
|
|
295
|
+
value = summary.get(key)
|
|
296
|
+
if isinstance(value, str) and value.strip():
|
|
297
|
+
recipient = value.strip()
|
|
298
|
+
break
|
|
299
|
+
|
|
300
|
+
spend: int | None = None
|
|
301
|
+
for key in ("input_amount_raw", "amount_raw", "amount_wei", "amount_lamports", "amount_base_units"):
|
|
302
|
+
value = summary.get(key)
|
|
303
|
+
if value is None:
|
|
304
|
+
continue
|
|
305
|
+
try:
|
|
306
|
+
spend = int(str(value))
|
|
307
|
+
except (TypeError, ValueError):
|
|
308
|
+
continue
|
|
309
|
+
break
|
|
310
|
+
return recipient, spend
|
|
311
|
+
|
|
312
|
+
def _build_session_config_from_args(self, args: dict[str, Any]):
|
|
313
|
+
"""Construct an AutonomousSessionConfig from start_autonomous_session args."""
|
|
314
|
+
from agent_wallet.autonomous_policy import AutonomousSessionConfig
|
|
315
|
+
from agent_wallet.spending_limits import SpendingConfig
|
|
316
|
+
|
|
317
|
+
def _str_list(value: Any, field: str) -> list[str]:
|
|
318
|
+
if value is None:
|
|
319
|
+
return []
|
|
320
|
+
if not isinstance(value, list) or any(not isinstance(v, str) or not v.strip() for v in value):
|
|
321
|
+
raise WalletBackendError(f"{field} must be an array of non-empty strings.")
|
|
322
|
+
return [v.strip() for v in value]
|
|
323
|
+
|
|
324
|
+
def _int(value: Any, field: str) -> int:
|
|
325
|
+
if value is None:
|
|
326
|
+
return 0
|
|
327
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
328
|
+
raise WalletBackendError(f"{field} must be a non-negative integer.")
|
|
329
|
+
return value
|
|
330
|
+
|
|
331
|
+
return AutonomousSessionConfig(
|
|
332
|
+
enabled=True,
|
|
333
|
+
allowed_tools=frozenset(_str_list(args.get("allowed_tools"), "allowed_tools")),
|
|
334
|
+
allowed_networks=frozenset(_str_list(args.get("allowed_networks"), "allowed_networks")),
|
|
335
|
+
allow_mainnet=bool(args.get("allow_mainnet", False)),
|
|
336
|
+
allowed_recipients=frozenset(_str_list(args.get("allowed_recipients"), "allowed_recipients")),
|
|
337
|
+
allow_any_recipient=bool(args.get("allow_any_recipient", False)),
|
|
338
|
+
require_simulation=bool(args.get("require_simulation", True)),
|
|
339
|
+
spending=SpendingConfig(
|
|
340
|
+
max_per_tx_lamports=_int(args.get("max_per_tx_lamports"), "max_per_tx_lamports"),
|
|
341
|
+
max_hourly_lamports=_int(args.get("max_hourly_lamports"), "max_hourly_lamports"),
|
|
342
|
+
max_daily_lamports=_int(args.get("max_daily_lamports"), "max_daily_lamports"),
|
|
343
|
+
max_txs_per_minute=_int(args.get("max_txs_per_minute"), "max_txs_per_minute"),
|
|
344
|
+
),
|
|
345
|
+
max_operations=_int(args.get("max_operations"), "max_operations"),
|
|
346
|
+
session_ttl_seconds=_int(args.get("session_ttl_seconds"), "session_ttl_seconds"),
|
|
347
|
+
approval_ttl_seconds=_int(args.get("approval_ttl_seconds"), "approval_ttl_seconds") or 120,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
async def _authorize_base_swap_permission(
|
|
351
|
+
self,
|
|
352
|
+
*,
|
|
353
|
+
active_backend: AgentWalletBackend,
|
|
354
|
+
tool_name: str,
|
|
355
|
+
action_label: str,
|
|
356
|
+
preview_kwargs: dict[str, Any],
|
|
357
|
+
preview_method: Any,
|
|
358
|
+
) -> tuple[str, dict[str, Any]]:
|
|
359
|
+
"""Authorize one Base swap through the high-trust permission toggle."""
|
|
360
|
+
from agent_wallet import autonomous_permissions
|
|
361
|
+
|
|
362
|
+
network = str(getattr(active_backend, "network", "unknown")).strip().lower()
|
|
363
|
+
if network != autonomous_permissions.BASE_SWAP_NETWORK:
|
|
364
|
+
raise WalletBackendError(
|
|
365
|
+
"Autonomous Base swap permission only applies on network=base. "
|
|
366
|
+
"Use set_evm_network or the network parameter to select Base."
|
|
367
|
+
)
|
|
368
|
+
if not autonomous_permissions.is_base_swap_approved():
|
|
369
|
+
raise WalletBackendError(
|
|
370
|
+
"Autonomous Base swap permission is not enabled. Ask the user to run "
|
|
371
|
+
"agentlayer_autonomous_approve with scope=base_swaps first."
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
preview = await preview_method(**preview_kwargs)
|
|
375
|
+
annotated_preview = self._annotate_sensitive_payload(
|
|
376
|
+
preview,
|
|
377
|
+
action_label=action_label,
|
|
378
|
+
mode="preview",
|
|
379
|
+
)
|
|
380
|
+
summary = dict(annotated_preview.get("confirmation_summary") or {})
|
|
381
|
+
if not summary:
|
|
382
|
+
raise WalletBackendError("Autonomous Base swap preview did not produce a confirmation_summary.")
|
|
383
|
+
token = autonomous_permissions.authorize_base_swap(
|
|
384
|
+
tool_name=tool_name,
|
|
385
|
+
network=network,
|
|
386
|
+
summary=summary,
|
|
387
|
+
)
|
|
388
|
+
return token, summary
|
|
389
|
+
|
|
281
390
|
def _require_execute_approval(
|
|
282
391
|
self,
|
|
283
392
|
*,
|
|
@@ -288,10 +397,31 @@ class OpenClawWalletAdapter:
|
|
|
288
397
|
backend: AgentWalletBackend | None = None,
|
|
289
398
|
) -> None:
|
|
290
399
|
active_backend = backend or self.backend
|
|
400
|
+
network = str(getattr(active_backend, "network", "unknown"))
|
|
291
401
|
if not isinstance(approval_token, str) or not approval_token.strip():
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
402
|
+
# No host token: fall back to the autonomous policy engine if (and
|
|
403
|
+
# only if) a human-authorized session envelope is active. The engine
|
|
404
|
+
# mints the same signed token the host would, so verification below
|
|
405
|
+
# is identical for both paths.
|
|
406
|
+
from agent_wallet import autonomous_session
|
|
407
|
+
|
|
408
|
+
if autonomous_session.is_active():
|
|
409
|
+
recipient, spend = self._autonomous_signals(summary)
|
|
410
|
+
approval_token = autonomous_session.authorize_operation(
|
|
411
|
+
OperationRequest(
|
|
412
|
+
tool_name=tool_name,
|
|
413
|
+
network=network,
|
|
414
|
+
summary=summary,
|
|
415
|
+
spend_amount=spend,
|
|
416
|
+
recipient=recipient,
|
|
417
|
+
simulated=True,
|
|
418
|
+
)
|
|
419
|
+
)
|
|
420
|
+
else:
|
|
421
|
+
raise WalletBackendError(
|
|
422
|
+
f"{action_label} execution requires a host-issued approval_token "
|
|
423
|
+
"(or an active autonomous session authorized by the host)."
|
|
424
|
+
)
|
|
295
425
|
verify_approval_token(
|
|
296
426
|
approval_token.strip(),
|
|
297
427
|
tool_name=tool_name,
|
|
@@ -371,6 +501,24 @@ class OpenClawWalletAdapter:
|
|
|
371
501
|
"spend_policy": payload.get("spend_policy"),
|
|
372
502
|
}
|
|
373
503
|
|
|
504
|
+
if asset_type == "kamino-lend-intent":
|
|
505
|
+
summary = {
|
|
506
|
+
"operation": action_label,
|
|
507
|
+
"network": str(payload.get("network") or getattr(self.backend, "network", "unknown")),
|
|
508
|
+
"owner": payload.get("owner"),
|
|
509
|
+
"kamino_operation": payload.get("kamino_operation"),
|
|
510
|
+
"market": payload.get("market"),
|
|
511
|
+
"reserve": payload.get("reserve"),
|
|
512
|
+
"amount_ui": payload.get("amount_ui"),
|
|
513
|
+
"recipient_policy": payload.get("recipient_policy"),
|
|
514
|
+
"spend_policy": payload.get("spend_policy"),
|
|
515
|
+
"valid_until_epoch_seconds": payload.get("valid_until_epoch_seconds"),
|
|
516
|
+
}
|
|
517
|
+
obligation_address = payload.get("obligation_address")
|
|
518
|
+
if obligation_address is not None:
|
|
519
|
+
summary["obligation_address"] = obligation_address
|
|
520
|
+
return summary
|
|
521
|
+
|
|
374
522
|
if asset_type == "solana-lifi-cross-chain-swap":
|
|
375
523
|
return {
|
|
376
524
|
"operation": action_label,
|
|
@@ -1804,7 +1952,8 @@ class OpenClawWalletAdapter:
|
|
|
1804
1952
|
name="swap_evm_tokens",
|
|
1805
1953
|
description=(
|
|
1806
1954
|
"Preview, prepare, or execute an ERC-20 or native ETH swap through Velora on supported EVM mainnet networks. "
|
|
1807
|
-
"Prepare returns an execution plan only, and execute requires a host-issued approval token bound to the previewed operation
|
|
1955
|
+
"Prepare returns an execution plan only, and execute requires a host-issued approval token bound to the previewed operation "
|
|
1956
|
+
"unless high-trust autonomous Base swaps are enabled with scope=base_swaps."
|
|
1808
1957
|
),
|
|
1809
1958
|
input_schema={
|
|
1810
1959
|
"type": "object",
|
|
@@ -1959,7 +2108,8 @@ class OpenClawWalletAdapter:
|
|
|
1959
2108
|
description=(
|
|
1960
2109
|
"Preview, prepare, or execute an ERC-20 or native ETH swap through the Uniswap Trading API "
|
|
1961
2110
|
"(CLASSIC routing) on ethereum or base. ERC-20 inputs use Permit2 EIP-712 signing automatically. "
|
|
1962
|
-
"Prepare returns an execution plan only. Execute requires a host-issued approval token bound to the previewed operation
|
|
2111
|
+
"Prepare returns an execution plan only. Execute requires a host-issued approval token bound to the previewed operation "
|
|
2112
|
+
"unless high-trust autonomous Base swaps are enabled with scope=base_swaps."
|
|
1963
2113
|
),
|
|
1964
2114
|
input_schema={
|
|
1965
2115
|
"type": "object",
|
|
@@ -2037,6 +2187,7 @@ class OpenClawWalletAdapter:
|
|
|
2037
2187
|
),
|
|
2038
2188
|
)
|
|
2039
2189
|
|
|
2190
|
+
tools.extend(self._autonomous_permission_tool_specs())
|
|
2040
2191
|
return tools
|
|
2041
2192
|
|
|
2042
2193
|
if capabilities.chain == "bitcoin":
|
|
@@ -3000,12 +3151,16 @@ class OpenClawWalletAdapter:
|
|
|
3000
3151
|
},
|
|
3001
3152
|
"mode": {
|
|
3002
3153
|
"type": "string",
|
|
3003
|
-
"enum": ["preview", "prepare", "execute"],
|
|
3004
|
-
"description": "
|
|
3154
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
3155
|
+
"description": "Prefer intent_preview then intent_execute after explicit chat confirmation: intent_execute re-derives the Kamino transaction and executes within the approved parameters without round-tripping the preview payload. Legacy preview/prepare/execute remains supported.",
|
|
3156
|
+
},
|
|
3157
|
+
"valid_for_seconds": {
|
|
3158
|
+
"type": "integer",
|
|
3159
|
+
"description": "Optional intent validity window in seconds for intent_preview (1-300, default 120).",
|
|
3005
3160
|
},
|
|
3006
3161
|
"purpose": {"type": "string", "description": "Short explanation of why the deposit is being made."},
|
|
3007
3162
|
"user_intent": {"type": "boolean", "description": "Must be true for prepare mode."},
|
|
3008
|
-
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute mode."},
|
|
3163
|
+
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute/intent_execute mode."},
|
|
3009
3164
|
},
|
|
3010
3165
|
"required": ["market", "reserve", "amount_ui", "mode", "purpose"],
|
|
3011
3166
|
"additionalProperties": False,
|
|
@@ -3038,12 +3193,16 @@ class OpenClawWalletAdapter:
|
|
|
3038
3193
|
},
|
|
3039
3194
|
"mode": {
|
|
3040
3195
|
"type": "string",
|
|
3041
|
-
"enum": ["preview", "prepare", "execute"],
|
|
3042
|
-
"description": "
|
|
3196
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
3197
|
+
"description": "Prefer intent_preview then intent_execute after explicit chat confirmation: intent_execute re-derives the Kamino transaction and executes within the approved parameters without round-tripping the preview payload. Legacy preview/prepare/execute remains supported.",
|
|
3198
|
+
},
|
|
3199
|
+
"valid_for_seconds": {
|
|
3200
|
+
"type": "integer",
|
|
3201
|
+
"description": "Optional intent validity window in seconds for intent_preview (1-300, default 120).",
|
|
3043
3202
|
},
|
|
3044
3203
|
"purpose": {"type": "string", "description": "Short explanation of why the withdraw is being made."},
|
|
3045
3204
|
"user_intent": {"type": "boolean", "description": "Must be true for prepare mode."},
|
|
3046
|
-
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute mode."},
|
|
3205
|
+
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute/intent_execute mode."},
|
|
3047
3206
|
},
|
|
3048
3207
|
"required": ["market", "reserve", "amount_ui", "mode", "purpose"],
|
|
3049
3208
|
"additionalProperties": False,
|
|
@@ -3076,12 +3235,16 @@ class OpenClawWalletAdapter:
|
|
|
3076
3235
|
},
|
|
3077
3236
|
"mode": {
|
|
3078
3237
|
"type": "string",
|
|
3079
|
-
"enum": ["preview", "prepare", "execute"],
|
|
3080
|
-
"description": "
|
|
3238
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
3239
|
+
"description": "Prefer intent_preview then intent_execute after explicit chat confirmation: intent_execute re-derives the Kamino transaction and executes within the approved parameters without round-tripping the preview payload. Legacy preview/prepare/execute remains supported.",
|
|
3240
|
+
},
|
|
3241
|
+
"valid_for_seconds": {
|
|
3242
|
+
"type": "integer",
|
|
3243
|
+
"description": "Optional intent validity window in seconds for intent_preview (1-300, default 120).",
|
|
3081
3244
|
},
|
|
3082
3245
|
"purpose": {"type": "string", "description": "Short explanation of why the borrow is being made."},
|
|
3083
3246
|
"user_intent": {"type": "boolean", "description": "Must be true for prepare mode."},
|
|
3084
|
-
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute mode."},
|
|
3247
|
+
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute/intent_execute mode."},
|
|
3085
3248
|
},
|
|
3086
3249
|
"required": ["market", "reserve", "amount_ui", "mode", "purpose"],
|
|
3087
3250
|
"additionalProperties": False,
|
|
@@ -3114,12 +3277,16 @@ class OpenClawWalletAdapter:
|
|
|
3114
3277
|
},
|
|
3115
3278
|
"mode": {
|
|
3116
3279
|
"type": "string",
|
|
3117
|
-
"enum": ["preview", "prepare", "execute"],
|
|
3118
|
-
"description": "
|
|
3280
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
3281
|
+
"description": "Prefer intent_preview then intent_execute after explicit chat confirmation: intent_execute re-derives the Kamino transaction and executes within the approved parameters without round-tripping the preview payload. Legacy preview/prepare/execute remains supported.",
|
|
3282
|
+
},
|
|
3283
|
+
"valid_for_seconds": {
|
|
3284
|
+
"type": "integer",
|
|
3285
|
+
"description": "Optional intent validity window in seconds for intent_preview (1-300, default 120).",
|
|
3119
3286
|
},
|
|
3120
3287
|
"purpose": {"type": "string", "description": "Short explanation of why the repay is being made."},
|
|
3121
3288
|
"user_intent": {"type": "boolean", "description": "Must be true for prepare mode."},
|
|
3122
|
-
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute mode."},
|
|
3289
|
+
"approval_token": {"type": "string", "description": "Host-issued approval token required for execute/intent_execute mode."},
|
|
3123
3290
|
},
|
|
3124
3291
|
"required": ["market", "reserve", "amount_ui", "mode", "purpose"],
|
|
3125
3292
|
"additionalProperties": False,
|
|
@@ -3259,6 +3426,176 @@ class OpenClawWalletAdapter:
|
|
|
3259
3426
|
)
|
|
3260
3427
|
)
|
|
3261
3428
|
|
|
3429
|
+
tools.append(
|
|
3430
|
+
AgentToolSpec(
|
|
3431
|
+
name="get_autonomous_session",
|
|
3432
|
+
description=(
|
|
3433
|
+
"Return the status of the current autonomous execution session, if any: "
|
|
3434
|
+
"whether it is active, its allow-lists, spend limits, operation count, and expiry. "
|
|
3435
|
+
"Read-only."
|
|
3436
|
+
),
|
|
3437
|
+
input_schema={
|
|
3438
|
+
"type": "object",
|
|
3439
|
+
"properties": {},
|
|
3440
|
+
"additionalProperties": False,
|
|
3441
|
+
},
|
|
3442
|
+
read_only=True,
|
|
3443
|
+
risk_level="low",
|
|
3444
|
+
)
|
|
3445
|
+
)
|
|
3446
|
+
tools.append(
|
|
3447
|
+
AgentToolSpec(
|
|
3448
|
+
name="start_autonomous_session",
|
|
3449
|
+
description=(
|
|
3450
|
+
"Start an autonomous execution session so subsequent wallet writes can execute "
|
|
3451
|
+
"WITHOUT a per-transaction human approval, bounded by the supplied limits. "
|
|
3452
|
+
"This grants standing authority, so it requires a host-issued approval_token bound "
|
|
3453
|
+
"to the exact session policy; an agent cannot start a session on its own. "
|
|
3454
|
+
"Preview the policy first (mode=preview) to obtain the confirmation summary the host signs."
|
|
3455
|
+
),
|
|
3456
|
+
input_schema={
|
|
3457
|
+
"type": "object",
|
|
3458
|
+
"properties": {
|
|
3459
|
+
"mode": {
|
|
3460
|
+
"type": "string",
|
|
3461
|
+
"enum": ["preview", "execute"],
|
|
3462
|
+
"description": "preview returns the policy summary to be confirmed; execute starts the session.",
|
|
3463
|
+
},
|
|
3464
|
+
"allowed_tools": {
|
|
3465
|
+
"type": "array",
|
|
3466
|
+
"items": {"type": "string"},
|
|
3467
|
+
"description": "Wallet tools the agent may execute autonomously.",
|
|
3468
|
+
},
|
|
3469
|
+
"allowed_networks": {
|
|
3470
|
+
"type": "array",
|
|
3471
|
+
"items": {"type": "string"},
|
|
3472
|
+
"description": "Networks the agent may operate on autonomously.",
|
|
3473
|
+
},
|
|
3474
|
+
"allow_mainnet": {
|
|
3475
|
+
"type": "boolean",
|
|
3476
|
+
"description": "Must be true to permit autonomous execution on real-money networks.",
|
|
3477
|
+
},
|
|
3478
|
+
"allowed_recipients": {
|
|
3479
|
+
"type": "array",
|
|
3480
|
+
"items": {"type": "string"},
|
|
3481
|
+
"description": "Destination addresses the agent may send to.",
|
|
3482
|
+
},
|
|
3483
|
+
"allow_any_recipient": {
|
|
3484
|
+
"type": "boolean",
|
|
3485
|
+
"description": "Allow destinations not on the allow-list (spend caps and simulation still apply).",
|
|
3486
|
+
},
|
|
3487
|
+
"require_simulation": {
|
|
3488
|
+
"type": "boolean",
|
|
3489
|
+
"description": "Require a passing simulation before each autonomous execute (default true).",
|
|
3490
|
+
},
|
|
3491
|
+
"max_per_tx_lamports": {"type": "integer", "description": "Per-transaction spend cap in smallest units (0 = unlimited)."},
|
|
3492
|
+
"max_hourly_lamports": {"type": "integer", "description": "Hourly cumulative spend cap (0 = unlimited)."},
|
|
3493
|
+
"max_daily_lamports": {"type": "integer", "description": "Daily cumulative spend cap (0 = unlimited)."},
|
|
3494
|
+
"max_txs_per_minute": {"type": "integer", "description": "Transaction rate cap per minute (0 = unlimited)."},
|
|
3495
|
+
"max_operations": {"type": "integer", "description": "Maximum operations the session may approve (0 = unlimited)."},
|
|
3496
|
+
"session_ttl_seconds": {"type": "integer", "description": "Session lifetime in seconds (0 = no expiry)."},
|
|
3497
|
+
"approval_ttl_seconds": {"type": "integer", "description": "TTL applied to each auto-issued approval token (default 120)."},
|
|
3498
|
+
"approval_token": {
|
|
3499
|
+
"type": "string",
|
|
3500
|
+
"description": "Host-issued approval token bound to this session policy. Required for mode=execute.",
|
|
3501
|
+
},
|
|
3502
|
+
},
|
|
3503
|
+
"required": ["mode"],
|
|
3504
|
+
"additionalProperties": False,
|
|
3505
|
+
},
|
|
3506
|
+
read_only=False,
|
|
3507
|
+
risk_level="high",
|
|
3508
|
+
)
|
|
3509
|
+
)
|
|
3510
|
+
tools.append(
|
|
3511
|
+
AgentToolSpec(
|
|
3512
|
+
name="stop_autonomous_session",
|
|
3513
|
+
description=(
|
|
3514
|
+
"End the current autonomous execution session. Always allowed; this only removes "
|
|
3515
|
+
"standing authority and returns the wallet to per-transaction human approval."
|
|
3516
|
+
),
|
|
3517
|
+
input_schema={
|
|
3518
|
+
"type": "object",
|
|
3519
|
+
"properties": {},
|
|
3520
|
+
"additionalProperties": False,
|
|
3521
|
+
},
|
|
3522
|
+
read_only=False,
|
|
3523
|
+
risk_level="low",
|
|
3524
|
+
)
|
|
3525
|
+
)
|
|
3526
|
+
tools.append(
|
|
3527
|
+
AgentToolSpec(
|
|
3528
|
+
name="agentlayer_autonomous_status",
|
|
3529
|
+
description=(
|
|
3530
|
+
"Return AgentLayer high-trust autonomous permission status. "
|
|
3531
|
+
"Currently supports only scope=base_swaps."
|
|
3532
|
+
),
|
|
3533
|
+
input_schema={
|
|
3534
|
+
"type": "object",
|
|
3535
|
+
"properties": {},
|
|
3536
|
+
"additionalProperties": False,
|
|
3537
|
+
},
|
|
3538
|
+
read_only=True,
|
|
3539
|
+
risk_level="low",
|
|
3540
|
+
)
|
|
3541
|
+
)
|
|
3542
|
+
tools.append(
|
|
3543
|
+
AgentToolSpec(
|
|
3544
|
+
name="agentlayer_autonomous_approve",
|
|
3545
|
+
description=(
|
|
3546
|
+
"Enable high-trust autonomous execution for a narrow scope. "
|
|
3547
|
+
"Currently scope=base_swaps lets Base Velora/Uniswap swap execute calls run "
|
|
3548
|
+
"without per-transaction human approval until revoked. This does not cover "
|
|
3549
|
+
"transfers, withdrawals, lending, staking, bridges, Solana swaps, or non-Base networks."
|
|
3550
|
+
),
|
|
3551
|
+
input_schema={
|
|
3552
|
+
"type": "object",
|
|
3553
|
+
"properties": {
|
|
3554
|
+
"scope": {
|
|
3555
|
+
"type": "string",
|
|
3556
|
+
"enum": ["base_swaps"],
|
|
3557
|
+
"description": "Only base_swaps is currently supported.",
|
|
3558
|
+
},
|
|
3559
|
+
"purpose": {
|
|
3560
|
+
"type": "string",
|
|
3561
|
+
"description": "Short explanation of why the standing permission is being enabled.",
|
|
3562
|
+
},
|
|
3563
|
+
"user_intent": {
|
|
3564
|
+
"type": "boolean",
|
|
3565
|
+
"description": "Must be true after the user explicitly asks to enable this permission.",
|
|
3566
|
+
},
|
|
3567
|
+
},
|
|
3568
|
+
"required": ["scope", "purpose", "user_intent"],
|
|
3569
|
+
"additionalProperties": False,
|
|
3570
|
+
},
|
|
3571
|
+
read_only=False,
|
|
3572
|
+
risk_level="high",
|
|
3573
|
+
)
|
|
3574
|
+
)
|
|
3575
|
+
tools.append(
|
|
3576
|
+
AgentToolSpec(
|
|
3577
|
+
name="agentlayer_autonomous_revoke",
|
|
3578
|
+
description=(
|
|
3579
|
+
"Disable high-trust autonomous execution for a scope. "
|
|
3580
|
+
"Currently supports only scope=base_swaps."
|
|
3581
|
+
),
|
|
3582
|
+
input_schema={
|
|
3583
|
+
"type": "object",
|
|
3584
|
+
"properties": {
|
|
3585
|
+
"scope": {
|
|
3586
|
+
"type": "string",
|
|
3587
|
+
"enum": ["base_swaps"],
|
|
3588
|
+
"description": "Only base_swaps is currently supported.",
|
|
3589
|
+
}
|
|
3590
|
+
},
|
|
3591
|
+
"required": ["scope"],
|
|
3592
|
+
"additionalProperties": False,
|
|
3593
|
+
},
|
|
3594
|
+
read_only=False,
|
|
3595
|
+
risk_level="low",
|
|
3596
|
+
)
|
|
3597
|
+
)
|
|
3598
|
+
|
|
3262
3599
|
tools.extend(self._x402_tool_specs())
|
|
3263
3600
|
return tools
|
|
3264
3601
|
|
|
@@ -3266,12 +3603,170 @@ class OpenClawWalletAdapter:
|
|
|
3266
3603
|
"""Return the instruction block to inject into the agent runtime."""
|
|
3267
3604
|
return WALLET_RUNTIME_INSTRUCTIONS
|
|
3268
3605
|
|
|
3606
|
+
@staticmethod
|
|
3607
|
+
def _autonomous_permission_tool_specs() -> list[AgentToolSpec]:
|
|
3608
|
+
return [
|
|
3609
|
+
AgentToolSpec(
|
|
3610
|
+
name="agentlayer_autonomous_status",
|
|
3611
|
+
description=(
|
|
3612
|
+
"Return AgentLayer high-trust autonomous permission status. "
|
|
3613
|
+
"Currently supports only scope=base_swaps."
|
|
3614
|
+
),
|
|
3615
|
+
input_schema={
|
|
3616
|
+
"type": "object",
|
|
3617
|
+
"properties": {},
|
|
3618
|
+
"additionalProperties": False,
|
|
3619
|
+
},
|
|
3620
|
+
read_only=True,
|
|
3621
|
+
risk_level="low",
|
|
3622
|
+
),
|
|
3623
|
+
AgentToolSpec(
|
|
3624
|
+
name="agentlayer_autonomous_approve",
|
|
3625
|
+
description=(
|
|
3626
|
+
"Enable high-trust autonomous execution for a narrow scope. "
|
|
3627
|
+
"Currently scope=base_swaps lets Base Velora/Uniswap swap execute calls run "
|
|
3628
|
+
"without per-transaction human approval until revoked. This does not cover "
|
|
3629
|
+
"transfers, withdrawals, lending, staking, bridges, Solana swaps, or non-Base networks."
|
|
3630
|
+
),
|
|
3631
|
+
input_schema={
|
|
3632
|
+
"type": "object",
|
|
3633
|
+
"properties": {
|
|
3634
|
+
"scope": {
|
|
3635
|
+
"type": "string",
|
|
3636
|
+
"enum": ["base_swaps"],
|
|
3637
|
+
"description": "Only base_swaps is currently supported.",
|
|
3638
|
+
},
|
|
3639
|
+
"purpose": {
|
|
3640
|
+
"type": "string",
|
|
3641
|
+
"description": "Short explanation of why the standing permission is being enabled.",
|
|
3642
|
+
},
|
|
3643
|
+
"user_intent": {
|
|
3644
|
+
"type": "boolean",
|
|
3645
|
+
"description": "Must be true after the user explicitly asks to enable this permission.",
|
|
3646
|
+
},
|
|
3647
|
+
},
|
|
3648
|
+
"required": ["scope", "purpose", "user_intent"],
|
|
3649
|
+
"additionalProperties": False,
|
|
3650
|
+
},
|
|
3651
|
+
read_only=False,
|
|
3652
|
+
risk_level="high",
|
|
3653
|
+
),
|
|
3654
|
+
AgentToolSpec(
|
|
3655
|
+
name="agentlayer_autonomous_revoke",
|
|
3656
|
+
description=(
|
|
3657
|
+
"Disable high-trust autonomous execution for a scope. "
|
|
3658
|
+
"Currently supports only scope=base_swaps."
|
|
3659
|
+
),
|
|
3660
|
+
input_schema={
|
|
3661
|
+
"type": "object",
|
|
3662
|
+
"properties": {
|
|
3663
|
+
"scope": {
|
|
3664
|
+
"type": "string",
|
|
3665
|
+
"enum": ["base_swaps"],
|
|
3666
|
+
"description": "Only base_swaps is currently supported.",
|
|
3667
|
+
}
|
|
3668
|
+
},
|
|
3669
|
+
"required": ["scope"],
|
|
3670
|
+
"additionalProperties": False,
|
|
3671
|
+
},
|
|
3672
|
+
read_only=False,
|
|
3673
|
+
risk_level="low",
|
|
3674
|
+
),
|
|
3675
|
+
]
|
|
3676
|
+
|
|
3269
3677
|
async def invoke(self, tool_name: str, arguments: dict[str, Any] | None = None) -> AgentToolResult:
|
|
3270
3678
|
"""Dispatch an agent-facing tool call to the wallet backend."""
|
|
3271
3679
|
args = arguments or {}
|
|
3272
3680
|
try:
|
|
3273
3681
|
active_backend = self._resolve_backend_for_args(args)
|
|
3274
3682
|
|
|
3683
|
+
if tool_name == "get_autonomous_session":
|
|
3684
|
+
from agent_wallet import autonomous_session
|
|
3685
|
+
|
|
3686
|
+
return AgentToolResult(tool=tool_name, ok=True, data=autonomous_session.session_status())
|
|
3687
|
+
|
|
3688
|
+
if tool_name == "stop_autonomous_session":
|
|
3689
|
+
from agent_wallet import autonomous_session
|
|
3690
|
+
|
|
3691
|
+
return AgentToolResult(tool=tool_name, ok=True, data=autonomous_session.stop_session())
|
|
3692
|
+
|
|
3693
|
+
if tool_name == "agentlayer_autonomous_status":
|
|
3694
|
+
from agent_wallet import autonomous_permissions
|
|
3695
|
+
|
|
3696
|
+
return AgentToolResult(tool=tool_name, ok=True, data=autonomous_permissions.status())
|
|
3697
|
+
|
|
3698
|
+
if tool_name == "agentlayer_autonomous_approve":
|
|
3699
|
+
from agent_wallet import autonomous_permissions
|
|
3700
|
+
|
|
3701
|
+
scope = str(args.get("scope") or "").strip()
|
|
3702
|
+
purpose = args.get("purpose")
|
|
3703
|
+
if scope != autonomous_permissions.BASE_SWAP_SCOPE:
|
|
3704
|
+
raise WalletBackendError("Only scope=base_swaps is currently supported.")
|
|
3705
|
+
if not isinstance(purpose, str) or not purpose.strip():
|
|
3706
|
+
raise WalletBackendError("purpose is required.")
|
|
3707
|
+
if args.get("user_intent") is not True:
|
|
3708
|
+
raise WalletBackendError(
|
|
3709
|
+
"agentlayer_autonomous_approve requires user_intent=true after the user explicitly asks for this permission."
|
|
3710
|
+
)
|
|
3711
|
+
return AgentToolResult(
|
|
3712
|
+
tool=tool_name,
|
|
3713
|
+
ok=True,
|
|
3714
|
+
data=autonomous_permissions.approve_base_swaps(approved_by="agentlayer_autonomous_approve"),
|
|
3715
|
+
)
|
|
3716
|
+
|
|
3717
|
+
if tool_name == "agentlayer_autonomous_revoke":
|
|
3718
|
+
from agent_wallet import autonomous_permissions
|
|
3719
|
+
|
|
3720
|
+
scope = str(args.get("scope") or "").strip()
|
|
3721
|
+
if scope != autonomous_permissions.BASE_SWAP_SCOPE:
|
|
3722
|
+
raise WalletBackendError("Only scope=base_swaps is currently supported.")
|
|
3723
|
+
return AgentToolResult(
|
|
3724
|
+
tool=tool_name,
|
|
3725
|
+
ok=True,
|
|
3726
|
+
data=autonomous_permissions.revoke_base_swaps(),
|
|
3727
|
+
)
|
|
3728
|
+
|
|
3729
|
+
if tool_name == "start_autonomous_session":
|
|
3730
|
+
from agent_wallet import autonomous_session
|
|
3731
|
+
from agent_wallet.autonomous_session import config_to_dict
|
|
3732
|
+
from agent_wallet.nonce_registry import require_single_use
|
|
3733
|
+
|
|
3734
|
+
config = self._build_session_config_from_args(args)
|
|
3735
|
+
policy_summary = {"operation": "start_autonomous_session", **config_to_dict(config)}
|
|
3736
|
+
mode = str(args.get("mode") or "").strip().lower()
|
|
3737
|
+
if mode not in {"preview", "execute"}:
|
|
3738
|
+
raise WalletBackendError("mode must be 'preview' or 'execute'.")
|
|
3739
|
+
if mode == "preview":
|
|
3740
|
+
return AgentToolResult(
|
|
3741
|
+
tool=tool_name,
|
|
3742
|
+
ok=True,
|
|
3743
|
+
data={
|
|
3744
|
+
"mode": "preview",
|
|
3745
|
+
"confirmation_summary": policy_summary,
|
|
3746
|
+
"execute_requires_approval_token": True,
|
|
3747
|
+
"approval_hint": {"host_must_issue_token_for": policy_summary},
|
|
3748
|
+
},
|
|
3749
|
+
)
|
|
3750
|
+
# execute: starting a session grants standing authority, so it
|
|
3751
|
+
# always requires a genuine host token — never the autonomous
|
|
3752
|
+
# fallback (an agent must not be able to widen its own envelope).
|
|
3753
|
+
approval_token = args.get("approval_token")
|
|
3754
|
+
if not isinstance(approval_token, str) or not approval_token.strip():
|
|
3755
|
+
raise WalletBackendError(
|
|
3756
|
+
"start_autonomous_session execute requires a host-issued approval_token "
|
|
3757
|
+
"bound to the previewed session policy."
|
|
3758
|
+
)
|
|
3759
|
+
verify_approval_token(
|
|
3760
|
+
approval_token.strip(),
|
|
3761
|
+
tool_name="start_autonomous_session",
|
|
3762
|
+
network=str(getattr(active_backend, "network", "unknown")),
|
|
3763
|
+
summary=policy_summary,
|
|
3764
|
+
require_mainnet_confirmation=bool(config.allow_mainnet),
|
|
3765
|
+
)
|
|
3766
|
+
require_single_use(approval_token.strip())
|
|
3767
|
+
status = autonomous_session.start_session(config)
|
|
3768
|
+
return AgentToolResult(tool=tool_name, ok=True, data=status)
|
|
3769
|
+
|
|
3275
3770
|
if tool_name == "x402_search_services":
|
|
3276
3771
|
query = args.get("query")
|
|
3277
3772
|
discovery_provider = args.get("discovery_provider", "auto")
|
|
@@ -4368,16 +4863,26 @@ class OpenClawWalletAdapter:
|
|
|
4368
4863
|
),
|
|
4369
4864
|
)
|
|
4370
4865
|
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4866
|
+
if isinstance(approval_token, str) and approval_token.strip():
|
|
4867
|
+
approval_payload = inspect_approval_token(
|
|
4868
|
+
approval_token,
|
|
4869
|
+
tool_name=tool_name,
|
|
4870
|
+
network=str(getattr(active_backend, "network", "unknown")),
|
|
4871
|
+
require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
|
|
4872
|
+
)
|
|
4873
|
+
approval_summary = approval_payload.get("binding", {}).get("summary")
|
|
4874
|
+
if not isinstance(approval_summary, dict):
|
|
4875
|
+
raise WalletBackendError(
|
|
4876
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4877
|
+
)
|
|
4878
|
+
approval_summary_copy = dict(approval_summary)
|
|
4879
|
+
else:
|
|
4880
|
+
approval_token, approval_summary_copy = await self._authorize_base_swap_permission(
|
|
4881
|
+
active_backend=active_backend,
|
|
4882
|
+
tool_name=tool_name,
|
|
4883
|
+
action_label="EVM swap",
|
|
4884
|
+
preview_kwargs=preview_kwargs,
|
|
4885
|
+
preview_method=active_backend.preview_evm_swap,
|
|
4381
4886
|
)
|
|
4382
4887
|
expected_summary = {
|
|
4383
4888
|
"operation": "EVM swap",
|
|
@@ -4387,12 +4892,11 @@ class OpenClawWalletAdapter:
|
|
|
4387
4892
|
"input_amount_raw": amount_in_raw.strip(),
|
|
4388
4893
|
}
|
|
4389
4894
|
for key, expected_value in expected_summary.items():
|
|
4390
|
-
if
|
|
4895
|
+
if approval_summary_copy.get(key) != expected_value:
|
|
4391
4896
|
raise WalletBackendError(
|
|
4392
4897
|
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4393
4898
|
)
|
|
4394
4899
|
|
|
4395
|
-
approval_summary_copy = dict(approval_summary)
|
|
4396
4900
|
self._require_execute_approval(
|
|
4397
4901
|
approval_token=approval_token,
|
|
4398
4902
|
tool_name=tool_name,
|
|
@@ -4674,16 +5178,26 @@ class OpenClawWalletAdapter:
|
|
|
4674
5178
|
),
|
|
4675
5179
|
)
|
|
4676
5180
|
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
5181
|
+
if isinstance(approval_token, str) and approval_token.strip():
|
|
5182
|
+
approval_payload = inspect_approval_token(
|
|
5183
|
+
approval_token,
|
|
5184
|
+
tool_name=tool_name,
|
|
5185
|
+
network=str(getattr(active_backend, "network", "unknown")),
|
|
5186
|
+
require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
|
|
5187
|
+
)
|
|
5188
|
+
approval_summary = approval_payload.get("binding", {}).get("summary")
|
|
5189
|
+
if not isinstance(approval_summary, dict):
|
|
5190
|
+
raise WalletBackendError(
|
|
5191
|
+
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
5192
|
+
)
|
|
5193
|
+
approval_summary_copy = dict(approval_summary)
|
|
5194
|
+
else:
|
|
5195
|
+
approval_token, approval_summary_copy = await self._authorize_base_swap_permission(
|
|
5196
|
+
active_backend=active_backend,
|
|
5197
|
+
tool_name=tool_name,
|
|
5198
|
+
action_label="Uniswap swap",
|
|
5199
|
+
preview_kwargs=preview_kwargs,
|
|
5200
|
+
preview_method=active_backend.preview_uniswap_swap,
|
|
4687
5201
|
)
|
|
4688
5202
|
expected_summary = {
|
|
4689
5203
|
"operation": "Uniswap swap",
|
|
@@ -4693,12 +5207,11 @@ class OpenClawWalletAdapter:
|
|
|
4693
5207
|
"input_amount_raw": amount_in_raw.strip(),
|
|
4694
5208
|
}
|
|
4695
5209
|
for key, expected_value in expected_summary.items():
|
|
4696
|
-
if
|
|
5210
|
+
if approval_summary_copy.get(key) != expected_value:
|
|
4697
5211
|
raise WalletBackendError(
|
|
4698
5212
|
"approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
|
|
4699
5213
|
)
|
|
4700
5214
|
|
|
4701
|
-
approval_summary_copy = dict(approval_summary)
|
|
4702
5215
|
self._require_execute_approval(
|
|
4703
5216
|
approval_token=approval_token,
|
|
4704
5217
|
tool_name=tool_name,
|
|
@@ -5480,6 +5993,7 @@ class OpenClawWalletAdapter:
|
|
|
5480
5993
|
purpose = args.get("purpose")
|
|
5481
5994
|
user_intent = args.get("user_intent", False)
|
|
5482
5995
|
approval_token = args.get("approval_token")
|
|
5996
|
+
valid_for_seconds = args.get("valid_for_seconds", 120)
|
|
5483
5997
|
|
|
5484
5998
|
if not isinstance(market, str) or not market.strip():
|
|
5485
5999
|
raise WalletBackendError("market is required.")
|
|
@@ -5489,10 +6003,18 @@ class OpenClawWalletAdapter:
|
|
|
5489
6003
|
raise WalletBackendError("amount_ui is required.")
|
|
5490
6004
|
if obligation_address is not None and not isinstance(obligation_address, str):
|
|
5491
6005
|
raise WalletBackendError("obligation_address must be a string when provided.")
|
|
5492
|
-
if mode not in {"preview", "prepare", "execute"}:
|
|
5493
|
-
raise WalletBackendError(
|
|
6006
|
+
if mode not in {"preview", "prepare", "execute", "intent_preview", "intent_execute"}:
|
|
6007
|
+
raise WalletBackendError(
|
|
6008
|
+
"mode must be 'preview', 'prepare', 'execute', 'intent_preview' or 'intent_execute'."
|
|
6009
|
+
)
|
|
5494
6010
|
if not isinstance(purpose, str) or not purpose.strip():
|
|
5495
6011
|
raise WalletBackendError("purpose is required.")
|
|
6012
|
+
if mode == "intent_preview" and (
|
|
6013
|
+
not isinstance(valid_for_seconds, int)
|
|
6014
|
+
or valid_for_seconds <= 0
|
|
6015
|
+
or valid_for_seconds > 300
|
|
6016
|
+
):
|
|
6017
|
+
raise WalletBackendError("valid_for_seconds must be an integer between 1 and 300.")
|
|
5496
6018
|
|
|
5497
6019
|
action_label_map = {
|
|
5498
6020
|
"kamino_lend_deposit": "Kamino deposit",
|
|
@@ -5500,12 +6022,24 @@ class OpenClawWalletAdapter:
|
|
|
5500
6022
|
"kamino_lend_borrow": "Kamino borrow",
|
|
5501
6023
|
"kamino_lend_repay": "Kamino repay",
|
|
5502
6024
|
}
|
|
6025
|
+
intent_action_label_map = {
|
|
6026
|
+
"kamino_lend_deposit": "Kamino deposit intent",
|
|
6027
|
+
"kamino_lend_withdraw": "Kamino withdraw intent",
|
|
6028
|
+
"kamino_lend_borrow": "Kamino borrow intent",
|
|
6029
|
+
"kamino_lend_repay": "Kamino repay intent",
|
|
6030
|
+
}
|
|
5503
6031
|
preview_method_map = {
|
|
5504
6032
|
"kamino_lend_deposit": self.backend.preview_kamino_lend_deposit,
|
|
5505
6033
|
"kamino_lend_withdraw": self.backend.preview_kamino_lend_withdraw,
|
|
5506
6034
|
"kamino_lend_borrow": self.backend.preview_kamino_lend_borrow,
|
|
5507
6035
|
"kamino_lend_repay": self.backend.preview_kamino_lend_repay,
|
|
5508
6036
|
}
|
|
6037
|
+
intent_preview_method_map = {
|
|
6038
|
+
"kamino_lend_deposit": self.backend.preview_kamino_lend_deposit_intent,
|
|
6039
|
+
"kamino_lend_withdraw": self.backend.preview_kamino_lend_withdraw_intent,
|
|
6040
|
+
"kamino_lend_borrow": self.backend.preview_kamino_lend_borrow_intent,
|
|
6041
|
+
"kamino_lend_repay": self.backend.preview_kamino_lend_repay_intent,
|
|
6042
|
+
}
|
|
5509
6043
|
execute_method_map = {
|
|
5510
6044
|
"kamino_lend_deposit": self.backend.execute_kamino_lend_deposit,
|
|
5511
6045
|
"kamino_lend_withdraw": self.backend.execute_kamino_lend_withdraw,
|
|
@@ -5513,8 +6047,113 @@ class OpenClawWalletAdapter:
|
|
|
5513
6047
|
"kamino_lend_repay": self.backend.execute_kamino_lend_repay,
|
|
5514
6048
|
}
|
|
5515
6049
|
action_label = action_label_map[tool_name]
|
|
6050
|
+
intent_action_label = intent_action_label_map[tool_name]
|
|
5516
6051
|
preview_method = preview_method_map[tool_name]
|
|
6052
|
+
intent_preview_method = intent_preview_method_map[tool_name]
|
|
5517
6053
|
execute_method = execute_method_map[tool_name]
|
|
6054
|
+
normalized_obligation_address = (
|
|
6055
|
+
obligation_address.strip()
|
|
6056
|
+
if isinstance(obligation_address, str) and obligation_address.strip()
|
|
6057
|
+
else None
|
|
6058
|
+
)
|
|
6059
|
+
|
|
6060
|
+
if mode == "intent_preview":
|
|
6061
|
+
intent_preview = await intent_preview_method(
|
|
6062
|
+
market=market.strip(),
|
|
6063
|
+
reserve=reserve.strip(),
|
|
6064
|
+
amount_ui=amount_ui.strip(),
|
|
6065
|
+
obligation_address=normalized_obligation_address,
|
|
6066
|
+
valid_for_seconds=valid_for_seconds,
|
|
6067
|
+
)
|
|
6068
|
+
if bool(intent_preview.get("requires_obligation_address")):
|
|
6069
|
+
raise WalletBackendError(
|
|
6070
|
+
f"{action_label} requires obligation_address when multiple Kamino obligations match the selected position."
|
|
6071
|
+
)
|
|
6072
|
+
return AgentToolResult(
|
|
6073
|
+
tool=tool_name,
|
|
6074
|
+
ok=True,
|
|
6075
|
+
data=self._annotate_sensitive_payload(
|
|
6076
|
+
intent_preview,
|
|
6077
|
+
action_label=intent_action_label,
|
|
6078
|
+
mode="preview",
|
|
6079
|
+
),
|
|
6080
|
+
)
|
|
6081
|
+
|
|
6082
|
+
if mode == "intent_execute":
|
|
6083
|
+
approval_payload = inspect_approval_token(
|
|
6084
|
+
approval_token,
|
|
6085
|
+
tool_name=tool_name,
|
|
6086
|
+
network=str(getattr(self.backend, "network", "unknown")),
|
|
6087
|
+
require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
|
|
6088
|
+
)
|
|
6089
|
+
approval_summary = approval_payload.get("binding", {}).get("summary")
|
|
6090
|
+
if not isinstance(approval_summary, dict):
|
|
6091
|
+
raise WalletBackendError(
|
|
6092
|
+
"approval_token does not match the requested operation. Generate a new intent preview and approval before execute."
|
|
6093
|
+
)
|
|
6094
|
+
expected_summary = {
|
|
6095
|
+
"operation": intent_action_label,
|
|
6096
|
+
"network": str(getattr(self.backend, "network", "unknown")),
|
|
6097
|
+
"market": market.strip(),
|
|
6098
|
+
"reserve": reserve.strip(),
|
|
6099
|
+
"amount_ui": amount_ui.strip(),
|
|
6100
|
+
}
|
|
6101
|
+
for key, expected_value in expected_summary.items():
|
|
6102
|
+
if approval_summary.get(key) != expected_value:
|
|
6103
|
+
raise WalletBackendError(
|
|
6104
|
+
f"approval_token does not match the requested {action_label} intent. Generate a fresh intent preview and approval before execute."
|
|
6105
|
+
)
|
|
6106
|
+
if approval_summary.get("recipient_policy") != "owner-only":
|
|
6107
|
+
raise WalletBackendError("approved Kamino intent recipient policy is invalid.")
|
|
6108
|
+
if approval_summary.get("spend_policy") != "exact-amount":
|
|
6109
|
+
raise WalletBackendError("approved Kamino intent spend policy is invalid.")
|
|
6110
|
+
current_owner = await self.backend.get_address()
|
|
6111
|
+
approved_owner = approval_summary.get("owner")
|
|
6112
|
+
if approved_owner and current_owner and str(approved_owner) != str(current_owner):
|
|
6113
|
+
raise WalletBackendError(
|
|
6114
|
+
"approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
|
|
6115
|
+
)
|
|
6116
|
+
approved_obligation = approval_summary.get("obligation_address")
|
|
6117
|
+
if (
|
|
6118
|
+
normalized_obligation_address is not None
|
|
6119
|
+
and approved_obligation is not None
|
|
6120
|
+
and str(approved_obligation) != normalized_obligation_address
|
|
6121
|
+
):
|
|
6122
|
+
raise WalletBackendError(
|
|
6123
|
+
"approval_token does not match the requested obligation. Generate a fresh intent preview and approval before execute."
|
|
6124
|
+
)
|
|
6125
|
+
valid_until = approval_summary.get("valid_until_epoch_seconds")
|
|
6126
|
+
if valid_until is not None and int(time.time()) > int(valid_until):
|
|
6127
|
+
raise WalletBackendError(
|
|
6128
|
+
"Approved Kamino intent has expired. Create a fresh intent preview."
|
|
6129
|
+
)
|
|
6130
|
+
approval_summary_copy = dict(approval_summary)
|
|
6131
|
+
self._require_execute_approval(
|
|
6132
|
+
approval_token=approval_token,
|
|
6133
|
+
tool_name=tool_name,
|
|
6134
|
+
summary=approval_summary_copy,
|
|
6135
|
+
action_label=intent_action_label,
|
|
6136
|
+
)
|
|
6137
|
+
result = await execute_method(
|
|
6138
|
+
market=market.strip(),
|
|
6139
|
+
reserve=reserve.strip(),
|
|
6140
|
+
amount_ui=amount_ui.strip(),
|
|
6141
|
+
obligation_address=(
|
|
6142
|
+
str(approved_obligation).strip()
|
|
6143
|
+
if approved_obligation
|
|
6144
|
+
else normalized_obligation_address
|
|
6145
|
+
),
|
|
6146
|
+
approved_preview=None,
|
|
6147
|
+
)
|
|
6148
|
+
return AgentToolResult(
|
|
6149
|
+
tool=tool_name,
|
|
6150
|
+
ok=True,
|
|
6151
|
+
data=self._annotate_sensitive_payload(
|
|
6152
|
+
result,
|
|
6153
|
+
action_label=action_label,
|
|
6154
|
+
mode="execute",
|
|
6155
|
+
),
|
|
6156
|
+
)
|
|
5518
6157
|
|
|
5519
6158
|
if mode == "preview":
|
|
5520
6159
|
preview = await preview_method(
|