@agentlayer.tech/wallet 0.1.48 → 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.
@@ -8,6 +8,7 @@ import time
8
8
  from typing import Any
9
9
 
10
10
  from agent_wallet.approval import inspect_approval_token, verify_approval_token
11
+ from agent_wallet.autonomous_policy import OperationRequest
11
12
  from agent_wallet.exceptions import ProviderError
12
13
  from agent_wallet.models import AgentToolResult, AgentToolSpec
13
14
  from agent_wallet.providers import x402
@@ -279,6 +280,113 @@ class OpenClawWalletAdapter:
279
280
  ),
280
281
  ]
281
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
+
282
390
  def _require_execute_approval(
283
391
  self,
284
392
  *,
@@ -289,10 +397,31 @@ class OpenClawWalletAdapter:
289
397
  backend: AgentWalletBackend | None = None,
290
398
  ) -> None:
291
399
  active_backend = backend or self.backend
400
+ network = str(getattr(active_backend, "network", "unknown"))
292
401
  if not isinstance(approval_token, str) or not approval_token.strip():
293
- raise WalletBackendError(
294
- f"{action_label} execution requires a host-issued approval_token."
295
- )
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
+ )
296
425
  verify_approval_token(
297
426
  approval_token.strip(),
298
427
  tool_name=tool_name,
@@ -1823,7 +1952,8 @@ class OpenClawWalletAdapter:
1823
1952
  name="swap_evm_tokens",
1824
1953
  description=(
1825
1954
  "Preview, prepare, or execute an ERC-20 or native ETH swap through Velora on supported EVM mainnet networks. "
1826
- "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."
1827
1957
  ),
1828
1958
  input_schema={
1829
1959
  "type": "object",
@@ -1978,7 +2108,8 @@ class OpenClawWalletAdapter:
1978
2108
  description=(
1979
2109
  "Preview, prepare, or execute an ERC-20 or native ETH swap through the Uniswap Trading API "
1980
2110
  "(CLASSIC routing) on ethereum or base. ERC-20 inputs use Permit2 EIP-712 signing automatically. "
1981
- "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."
1982
2113
  ),
1983
2114
  input_schema={
1984
2115
  "type": "object",
@@ -2056,6 +2187,7 @@ class OpenClawWalletAdapter:
2056
2187
  ),
2057
2188
  )
2058
2189
 
2190
+ tools.extend(self._autonomous_permission_tool_specs())
2059
2191
  return tools
2060
2192
 
2061
2193
  if capabilities.chain == "bitcoin":
@@ -3294,6 +3426,176 @@ class OpenClawWalletAdapter:
3294
3426
  )
3295
3427
  )
3296
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
+
3297
3599
  tools.extend(self._x402_tool_specs())
3298
3600
  return tools
3299
3601
 
@@ -3301,12 +3603,170 @@ class OpenClawWalletAdapter:
3301
3603
  """Return the instruction block to inject into the agent runtime."""
3302
3604
  return WALLET_RUNTIME_INSTRUCTIONS
3303
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
+
3304
3677
  async def invoke(self, tool_name: str, arguments: dict[str, Any] | None = None) -> AgentToolResult:
3305
3678
  """Dispatch an agent-facing tool call to the wallet backend."""
3306
3679
  args = arguments or {}
3307
3680
  try:
3308
3681
  active_backend = self._resolve_backend_for_args(args)
3309
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
+
3310
3770
  if tool_name == "x402_search_services":
3311
3771
  query = args.get("query")
3312
3772
  discovery_provider = args.get("discovery_provider", "auto")
@@ -4403,16 +4863,26 @@ class OpenClawWalletAdapter:
4403
4863
  ),
4404
4864
  )
4405
4865
 
4406
- approval_payload = inspect_approval_token(
4407
- approval_token,
4408
- tool_name=tool_name,
4409
- network=str(getattr(active_backend, "network", "unknown")),
4410
- require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
4411
- )
4412
- approval_summary = approval_payload.get("binding", {}).get("summary")
4413
- if not isinstance(approval_summary, dict):
4414
- raise WalletBackendError(
4415
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
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,
4416
4886
  )
4417
4887
  expected_summary = {
4418
4888
  "operation": "EVM swap",
@@ -4422,12 +4892,11 @@ class OpenClawWalletAdapter:
4422
4892
  "input_amount_raw": amount_in_raw.strip(),
4423
4893
  }
4424
4894
  for key, expected_value in expected_summary.items():
4425
- if approval_summary.get(key) != expected_value:
4895
+ if approval_summary_copy.get(key) != expected_value:
4426
4896
  raise WalletBackendError(
4427
4897
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
4428
4898
  )
4429
4899
 
4430
- approval_summary_copy = dict(approval_summary)
4431
4900
  self._require_execute_approval(
4432
4901
  approval_token=approval_token,
4433
4902
  tool_name=tool_name,
@@ -4709,16 +5178,26 @@ class OpenClawWalletAdapter:
4709
5178
  ),
4710
5179
  )
4711
5180
 
4712
- approval_payload = inspect_approval_token(
4713
- approval_token,
4714
- tool_name=tool_name,
4715
- network=str(getattr(active_backend, "network", "unknown")),
4716
- require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
4717
- )
4718
- approval_summary = approval_payload.get("binding", {}).get("summary")
4719
- if not isinstance(approval_summary, dict):
4720
- raise WalletBackendError(
4721
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
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,
4722
5201
  )
4723
5202
  expected_summary = {
4724
5203
  "operation": "Uniswap swap",
@@ -4728,12 +5207,11 @@ class OpenClawWalletAdapter:
4728
5207
  "input_amount_raw": amount_in_raw.strip(),
4729
5208
  }
4730
5209
  for key, expected_value in expected_summary.items():
4731
- if approval_summary.get(key) != expected_value:
5210
+ if approval_summary_copy.get(key) != expected_value:
4732
5211
  raise WalletBackendError(
4733
5212
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
4734
5213
  )
4735
5214
 
4736
- approval_summary_copy = dict(approval_summary)
4737
5215
  self._require_execute_approval(
4738
5216
  approval_token=approval_token,
4739
5217
  tool_name=tool_name,