@agentlayer.tech/wallet 0.1.89 → 0.1.91

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.
Files changed (33) hide show
  1. package/.openclaw/extensions/agent-wallet/dist/index.js +5 -5
  2. package/.openclaw/extensions/agent-wallet/index.ts +5 -5
  3. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -1
  4. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  5. package/CHANGELOG.md +14 -0
  6. package/README.md +46 -23
  7. package/VERSION +1 -1
  8. package/agent-wallet/README.md +26 -1
  9. package/agent-wallet/UPGRADE_COMPATIBILITY.md +2 -1
  10. package/agent-wallet/agent_wallet/__init__.py +1 -1
  11. package/agent-wallet/agent_wallet/autonomous_permissions.py +43 -1
  12. package/agent-wallet/agent_wallet/openclaw_adapter.py +604 -375
  13. package/agent-wallet/agent_wallet/providers/x402.py +84 -0
  14. package/agent-wallet/openclaw.plugin.json +1 -1
  15. package/agent-wallet/pyproject.toml +1 -1
  16. package/agent-wallet/scripts/install_agent_wallet.py +197 -11
  17. package/agent-wallet/scripts/install_openclaw_local_config.py +4 -1
  18. package/agent-wallet/scripts/install_openclaw_sealed_keys.py +6 -2
  19. package/bin/lib/host-detection.mjs +236 -0
  20. package/bin/lib/integrations.mjs +69 -5
  21. package/bin/openclaw-agent-wallet.mjs +398 -28
  22. package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
  23. package/claude-code/plugins/agent-wallet/README.md +3 -1
  24. package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-approve.md +7 -8
  25. package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-revoke.md +2 -2
  26. package/claude-code/plugins/agent-wallet/commands/wallet-base.md +38 -15
  27. package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
  28. package/codex/plugins/agent-wallet/README.md +11 -6
  29. package/codex/plugins/agent-wallet/skills/wallet-base/SKILL.md +56 -0
  30. package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
  31. package/package.json +8 -3
  32. package/wdk-btc-wallet/package.json +1 -1
  33. package/wdk-evm-wallet/package.json +1 -1
@@ -281,6 +281,10 @@ class OpenClawWalletAdapter:
281
281
  "json_body": {},
282
282
  "text_body": {"type": "string"},
283
283
  "purpose": {"type": "string"},
284
+ "approval_token": {
285
+ "type": "string",
286
+ "description": "Host-issued approval token required for execute mode when the payment is mainnet/real-value and no autonomous session or permission grant covers it.",
287
+ },
284
288
  },
285
289
  "required": ["url", "purpose"],
286
290
  "additionalProperties": False,
@@ -493,10 +497,25 @@ class OpenClawWalletAdapter:
493
497
  )
494
498
  )
495
499
  else:
496
- raise WalletBackendError(
497
- f"{action_label} execution requires a host-issued approval_token "
498
- "(or an active autonomous session authorized by the host)."
499
- )
500
+ # No budgeted session either: fall back to the standing
501
+ # high-trust permission toggle (agentlayer_autonomous_approve).
502
+ # This is the single choke point every execute tool funnels
503
+ # through, so enabling that toggle covers every tool here --
504
+ # not just the Base-swap/EVM-DeFi tools that also have their
505
+ # own dedicated pre-authorization step before reaching here.
506
+ from agent_wallet import autonomous_permissions
507
+
508
+ if autonomous_permissions.is_autonomous_approved():
509
+ approval_token = autonomous_permissions.authorize_operation(
510
+ tool_name=tool_name,
511
+ network=network,
512
+ summary=summary,
513
+ )
514
+ else:
515
+ raise WalletBackendError(
516
+ f"{action_label} execution requires a host-issued approval_token "
517
+ "(or an active autonomous session/permission grant authorized by the host)."
518
+ )
500
519
  verify_approval_token(
501
520
  approval_token.strip(),
502
521
  tool_name=tool_name,
@@ -1147,19 +1166,29 @@ class OpenClawWalletAdapter:
1147
1166
  annotated["payment_summary"] = summary
1148
1167
  requirements = dict(annotated.get("confirmation_requirements") or {})
1149
1168
  requirements["prepare_requires_user_intent"] = False
1150
- requirements["execute_requires_approval_token"] = False
1151
- requirements["execute_requires_mainnet_confirmed_in_token"] = False
1169
+ de_minimis = x402.is_de_minimis_payment(payload)
1170
+ if de_minimis:
1171
+ # Below x402.DE_MINIMIS_USD_THRESHOLD, this payment never needs
1172
+ # approval -- not even on mainnet -- so keep the advertised
1173
+ # requirement honest in both preview and execute responses.
1174
+ requirements["execute_requires_approval_token"] = False
1175
+ requirements["execute_requires_mainnet_confirmed_in_token"] = False
1176
+ annotated["de_minimis_payment"] = {
1177
+ "applied": True,
1178
+ "threshold_usd": x402.DE_MINIMIS_USD_THRESHOLD,
1179
+ "amount_usd": x402.de_minimis_usd_amount(payload),
1180
+ }
1152
1181
  annotated.pop("approval_hint", None)
1153
1182
  if mode == "preview":
1154
1183
  annotated.pop("confirmation_summary", None)
1155
- annotated.pop("confirmation_requirements", None)
1156
- if annotated.get("is_mainnet"):
1184
+ annotated["confirmation_requirements"] = requirements
1185
+ if annotated.get("is_mainnet") and not de_minimis:
1157
1186
  annotated["preview_note"] = (
1158
1187
  "This is a paid mainnet endpoint preview only. Review the service URL, network, asset, amount, and payment destination before calling x402_pay_request."
1159
1188
  )
1160
1189
  return annotated
1161
1190
  annotated["confirmation_requirements"] = requirements
1162
- if annotated.get("is_mainnet"):
1191
+ if annotated.get("is_mainnet") and not de_minimis:
1163
1192
  annotated["mainnet_warning"] = (
1164
1193
  "Mainnet x402 payment. Confirm the service URL, network, asset, amount, and payment destination before paying."
1165
1194
  )
@@ -3867,7 +3896,9 @@ class OpenClawWalletAdapter:
3867
3896
  name="agentlayer_autonomous_status",
3868
3897
  description=(
3869
3898
  "Return AgentLayer high-trust autonomous permission status. "
3870
- "The autonomous permission group contains base_swaps and defi_tools."
3899
+ "The autonomous permission group covers every wallet write tool (transfers, "
3900
+ "bridges, Solana swaps, staking, x402 payments, generic contract calls, Base "
3901
+ "swaps, and EVM DeFi management), not just base_swaps/defi_tools."
3871
3902
  ),
3872
3903
  input_schema={
3873
3904
  "type": "object",
@@ -3883,17 +3914,18 @@ class OpenClawWalletAdapter:
3883
3914
  name="agentlayer_autonomous_approve",
3884
3915
  description=(
3885
3916
  "Enable the high-trust autonomous permission group. The scope parameter is kept "
3886
- "for compatibility; choosing base_swaps or defi_tools enables both Base swaps and "
3887
- "supported EVM DeFi management tools until revoked. This does not cover transfers, "
3888
- "bridges, Solana swaps, or generic contract calls."
3917
+ "for compatibility; choosing base_swaps or defi_tools enables the full group, "
3918
+ "which covers every wallet write tool -- transfers, bridges, Solana swaps, "
3919
+ "staking, x402 payments, generic contract calls, Base swaps, and supported EVM "
3920
+ "DeFi management tools -- until revoked."
3889
3921
  ),
3890
3922
  input_schema={
3891
3923
  "type": "object",
3892
3924
  "properties": {
3893
3925
  "scope": {
3894
3926
  "type": "string",
3895
- "enum": ["base_swaps", "defi_tools"],
3896
- "description": "Compatibility scope; either value enables the full autonomous permission group.",
3927
+ "enum": ["all", "base_swaps", "defi_tools"],
3928
+ "description": "Scope to enable. Use \"all\" (recommended) -- base_swaps and defi_tools are deprecated aliases that enable the exact same full autonomous permission group, covering every wallet write tool.",
3897
3929
  },
3898
3930
  "purpose": {
3899
3931
  "type": "string",
@@ -3923,8 +3955,8 @@ class OpenClawWalletAdapter:
3923
3955
  "properties": {
3924
3956
  "scope": {
3925
3957
  "type": "string",
3926
- "enum": ["base_swaps", "defi_tools"],
3927
- "description": "Compatibility scope; either value revokes the full autonomous permission group.",
3958
+ "enum": ["all", "base_swaps", "defi_tools"],
3959
+ "description": "Scope to revoke. Use \"all\" (recommended) -- base_swaps and defi_tools are deprecated aliases that revoke the exact same full autonomous permission group.",
3928
3960
  }
3929
3961
  },
3930
3962
  "required": ["scope"],
@@ -3949,7 +3981,9 @@ class OpenClawWalletAdapter:
3949
3981
  name="agentlayer_autonomous_status",
3950
3982
  description=(
3951
3983
  "Return AgentLayer high-trust autonomous permission status. "
3952
- "The autonomous permission group contains base_swaps and defi_tools."
3984
+ "The autonomous permission group covers every wallet write tool (transfers, "
3985
+ "bridges, Solana swaps, staking, x402 payments, generic contract calls, Base "
3986
+ "swaps, and EVM DeFi management), not just base_swaps/defi_tools."
3953
3987
  ),
3954
3988
  input_schema={
3955
3989
  "type": "object",
@@ -3963,17 +3997,18 @@ class OpenClawWalletAdapter:
3963
3997
  name="agentlayer_autonomous_approve",
3964
3998
  description=(
3965
3999
  "Enable the high-trust autonomous permission group. The scope parameter is kept "
3966
- "for compatibility; choosing base_swaps or defi_tools enables both Base swaps and "
3967
- "supported EVM DeFi management tools until revoked. This does not cover transfers, "
3968
- "bridges, Solana swaps, or generic contract calls."
4000
+ "for compatibility; choosing base_swaps or defi_tools enables the full group, "
4001
+ "which covers every wallet write tool -- transfers, bridges, Solana swaps, "
4002
+ "staking, x402 payments, generic contract calls, Base swaps, and supported EVM "
4003
+ "DeFi management tools -- until revoked."
3969
4004
  ),
3970
4005
  input_schema={
3971
4006
  "type": "object",
3972
4007
  "properties": {
3973
4008
  "scope": {
3974
4009
  "type": "string",
3975
- "enum": ["base_swaps", "defi_tools"],
3976
- "description": "Compatibility scope; either value enables the full autonomous permission group.",
4010
+ "enum": ["all", "base_swaps", "defi_tools"],
4011
+ "description": "Scope to enable. Use \"all\" (recommended) -- base_swaps and defi_tools are deprecated aliases that enable the exact same full autonomous permission group, covering every wallet write tool.",
3977
4012
  },
3978
4013
  "purpose": {
3979
4014
  "type": "string",
@@ -4001,8 +4036,8 @@ class OpenClawWalletAdapter:
4001
4036
  "properties": {
4002
4037
  "scope": {
4003
4038
  "type": "string",
4004
- "enum": ["base_swaps", "defi_tools"],
4005
- "description": "Compatibility scope; either value revokes the full autonomous permission group.",
4039
+ "enum": ["all", "base_swaps", "defi_tools"],
4040
+ "description": "Scope to revoke. Use \"all\" (recommended) -- base_swaps and defi_tools are deprecated aliases that revoke the exact same full autonomous permission group.",
4006
4041
  }
4007
4042
  },
4008
4043
  "required": ["scope"],
@@ -4040,7 +4075,7 @@ class OpenClawWalletAdapter:
4040
4075
  scope = str(args.get("scope") or "").strip()
4041
4076
  purpose = args.get("purpose")
4042
4077
  if scope not in autonomous_permissions.SUPPORTED_SCOPES:
4043
- raise WalletBackendError("scope must be one of: base_swaps, defi_tools.")
4078
+ raise WalletBackendError("scope must be one of: all, base_swaps, defi_tools.")
4044
4079
  if not isinstance(purpose, str) or not purpose.strip():
4045
4080
  raise WalletBackendError("purpose is required.")
4046
4081
  if args.get("user_intent") is not True:
@@ -4061,7 +4096,7 @@ class OpenClawWalletAdapter:
4061
4096
 
4062
4097
  scope = str(args.get("scope") or "").strip()
4063
4098
  if scope not in autonomous_permissions.SUPPORTED_SCOPES:
4064
- raise WalletBackendError("scope must be one of: base_swaps, defi_tools.")
4099
+ raise WalletBackendError("scope must be one of: all, base_swaps, defi_tools.")
4065
4100
  return AgentToolResult(
4066
4101
  tool=tool_name,
4067
4102
  ok=True,
@@ -4206,7 +4241,14 @@ class OpenClawWalletAdapter:
4206
4241
  approved_preview = args.get("_approved_preview")
4207
4242
  if approved_preview is not None and not isinstance(approved_preview, dict):
4208
4243
  raise WalletBackendError("_approved_preview must be an object when provided.")
4209
- data = await x402.pay_and_fetch(
4244
+ approval_token = args.get("approval_token")
4245
+
4246
+ # Resolve the exact payment about to be made -- reusing
4247
+ # _approved_preview when it still matches this request (same
4248
+ # fingerprint check pay_and_fetch applies internally), or a
4249
+ # fresh probe otherwise -- so the summary bound into the
4250
+ # approval token below is never stale or re-priced.
4251
+ preview_payload = await x402.resolve_payment_preview(
4210
4252
  backend=active_backend,
4211
4253
  url=url.strip(),
4212
4254
  method=method,
@@ -4216,6 +4258,32 @@ class OpenClawWalletAdapter:
4216
4258
  text_body=text_body,
4217
4259
  approved_preview=approved_preview,
4218
4260
  )
4261
+ de_minimis = False
4262
+ if preview_payload.get("payment_required"):
4263
+ de_minimis = x402.is_de_minimis_payment(preview_payload)
4264
+ if not de_minimis:
4265
+ summary = self._build_confirmation_summary(
4266
+ action_label="x402 paid request",
4267
+ payload=preview_payload,
4268
+ )
4269
+ self._require_execute_approval(
4270
+ approval_token=approval_token,
4271
+ tool_name=tool_name,
4272
+ summary=summary,
4273
+ action_label="x402 paid request",
4274
+ backend=active_backend,
4275
+ )
4276
+
4277
+ data = await x402.pay_and_fetch(
4278
+ backend=active_backend,
4279
+ url=url.strip(),
4280
+ method=method,
4281
+ headers=headers,
4282
+ query=query,
4283
+ json_body=json_body,
4284
+ text_body=text_body,
4285
+ approved_preview=preview_payload,
4286
+ )
4219
4287
  data["purpose"] = purpose.strip()
4220
4288
  data = self._annotate_x402_payload(data, mode="execute")
4221
4289
  return AgentToolResult(tool=tool_name, ok=True, data=data)
@@ -5410,57 +5478,68 @@ class OpenClawWalletAdapter:
5410
5478
  ),
5411
5479
  )
5412
5480
 
5413
- approval_payload = inspect_approval_token(
5414
- approval_token,
5415
- tool_name=tool_name,
5416
- network=str(getattr(active_backend, "network", "unknown")),
5417
- require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
5418
- )
5419
- approval_summary = approval_payload.get("binding", {}).get("summary")
5420
- if not isinstance(approval_summary, dict):
5421
- raise WalletBackendError(
5422
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5481
+ if isinstance(approval_token, str) and approval_token.strip():
5482
+ approval_payload = inspect_approval_token(
5483
+ approval_token,
5484
+ tool_name=tool_name,
5485
+ network=str(getattr(active_backend, "network", "unknown")),
5486
+ require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
5423
5487
  )
5424
- source_chain_id = self._canonicalize_lifi_chain_identifier(
5425
- getattr(active_backend, "network", "unknown")
5426
- )
5427
- destination_chain_id = self._canonicalize_lifi_chain_identifier(destination_chain)
5428
- expected_summary = {
5429
- "operation": "EVM LI.FI cross-chain swap",
5430
- "network": str(getattr(active_backend, "network", "unknown")),
5431
- "source_chain": source_chain_id,
5432
- "destination_chain": destination_chain_id,
5433
- "token_in": self._canonicalize_lifi_token_identifier(
5434
- token_in,
5435
- chain_id=source_chain_id,
5436
- ),
5437
- "output_token": self._canonicalize_lifi_token_identifier(
5438
- output_token,
5439
- chain_id=destination_chain_id,
5440
- ),
5441
- "destination_address": destination_address.strip(),
5442
- "input_amount_raw": amount_in_raw.strip(),
5443
- }
5444
- for key, expected_value in expected_summary.items():
5445
- actual_value = approval_summary.get(key)
5446
- if key in {"source_chain", "destination_chain"}:
5447
- actual_value = self._canonicalize_lifi_chain_identifier(actual_value)
5448
- if key == "token_in":
5449
- actual_value = self._canonicalize_lifi_token_identifier(
5450
- actual_value,
5451
- chain_id=source_chain_id,
5452
- )
5453
- if key == "output_token":
5454
- actual_value = self._canonicalize_lifi_token_identifier(
5455
- actual_value,
5456
- chain_id=destination_chain_id,
5457
- )
5458
- if actual_value != expected_value:
5488
+ approval_summary = approval_payload.get("binding", {}).get("summary")
5489
+ if not isinstance(approval_summary, dict):
5459
5490
  raise WalletBackendError(
5460
5491
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5461
5492
  )
5493
+ source_chain_id = self._canonicalize_lifi_chain_identifier(
5494
+ getattr(active_backend, "network", "unknown")
5495
+ )
5496
+ destination_chain_id = self._canonicalize_lifi_chain_identifier(destination_chain)
5497
+ expected_summary = {
5498
+ "operation": "EVM LI.FI cross-chain swap",
5499
+ "network": str(getattr(active_backend, "network", "unknown")),
5500
+ "source_chain": source_chain_id,
5501
+ "destination_chain": destination_chain_id,
5502
+ "token_in": self._canonicalize_lifi_token_identifier(
5503
+ token_in,
5504
+ chain_id=source_chain_id,
5505
+ ),
5506
+ "output_token": self._canonicalize_lifi_token_identifier(
5507
+ output_token,
5508
+ chain_id=destination_chain_id,
5509
+ ),
5510
+ "destination_address": destination_address.strip(),
5511
+ "input_amount_raw": amount_in_raw.strip(),
5512
+ }
5513
+ for key, expected_value in expected_summary.items():
5514
+ actual_value = approval_summary.get(key)
5515
+ if key in {"source_chain", "destination_chain"}:
5516
+ actual_value = self._canonicalize_lifi_chain_identifier(actual_value)
5517
+ if key == "token_in":
5518
+ actual_value = self._canonicalize_lifi_token_identifier(
5519
+ actual_value,
5520
+ chain_id=source_chain_id,
5521
+ )
5522
+ if key == "output_token":
5523
+ actual_value = self._canonicalize_lifi_token_identifier(
5524
+ actual_value,
5525
+ chain_id=destination_chain_id,
5526
+ )
5527
+ if actual_value != expected_value:
5528
+ raise WalletBackendError(
5529
+ "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5530
+ )
5462
5531
 
5463
- approval_summary_copy = dict(approval_summary)
5532
+ approval_summary_copy = dict(approval_summary)
5533
+ else:
5534
+ # No host token: build the exact preview a human would
5535
+ # have seen and let _require_execute_approval's
5536
+ # autonomous_session / agentlayer_autonomous_approve
5537
+ # fallback authorize it.
5538
+ fresh_preview = await active_backend.preview_evm_lifi_cross_chain_swap(**preview_kwargs)
5539
+ approval_summary_copy = self._build_confirmation_summary(
5540
+ action_label="EVM LI.FI cross-chain swap",
5541
+ payload=fresh_preview,
5542
+ )
5464
5543
  self._require_execute_approval(
5465
5544
  approval_token=approval_token,
5466
5545
  tool_name=tool_name,
@@ -5842,46 +5921,69 @@ class OpenClawWalletAdapter:
5842
5921
  ),
5843
5922
  )
5844
5923
 
5845
- approval_payload = inspect_approval_token(
5846
- approval_token,
5847
- tool_name=tool_name,
5848
- network=str(getattr(active_backend, "network", "unknown")),
5849
- require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
5850
- )
5851
- approval_summary = approval_payload.get("binding", {}).get("summary")
5852
- if not isinstance(approval_summary, dict):
5853
- raise WalletBackendError(
5854
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5924
+ execute_preview = None
5925
+ if isinstance(approval_token, str) and approval_token.strip():
5926
+ approval_payload = inspect_approval_token(
5927
+ approval_token,
5928
+ tool_name=tool_name,
5929
+ network=str(getattr(active_backend, "network", "unknown")),
5930
+ require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
5855
5931
  )
5856
- expected_summary = {
5857
- "operation": "Flash Trade open position",
5858
- "pool_name": pool_name.strip(),
5859
- "market_symbol": market_symbol.strip(),
5860
- "collateral_symbol": collateral_symbol.strip(),
5861
- "collateral_amount_raw": collateral_amount_raw.strip(),
5862
- "leverage": leverage.strip(),
5863
- "side": side,
5864
- }
5865
- for key, expected_value in expected_summary.items():
5866
- if approval_summary.get(key) != expected_value:
5932
+ approval_summary = approval_payload.get("binding", {}).get("summary")
5933
+ if not isinstance(approval_summary, dict):
5867
5934
  raise WalletBackendError(
5868
5935
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5869
5936
  )
5937
+ expected_summary = {
5938
+ "operation": "Flash Trade open position",
5939
+ "pool_name": pool_name.strip(),
5940
+ "market_symbol": market_symbol.strip(),
5941
+ "collateral_symbol": collateral_symbol.strip(),
5942
+ "collateral_amount_raw": collateral_amount_raw.strip(),
5943
+ "leverage": leverage.strip(),
5944
+ "side": side,
5945
+ }
5946
+ for key, expected_value in expected_summary.items():
5947
+ if approval_summary.get(key) != expected_value:
5948
+ raise WalletBackendError(
5949
+ "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5950
+ )
5870
5951
 
5871
- approval_summary_copy = dict(approval_summary)
5872
- approved_preview = args.get("_approved_preview")
5873
- execute_preview = None
5874
- if isinstance(approval_summary_copy.get("_preview_digest"), str):
5875
- if not isinstance(approved_preview, dict):
5876
- raise WalletBackendError(
5877
- "Approved Flash Trade preview payload is required for execute mode. Generate a new preview and approval before execute."
5878
- )
5879
- if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
5880
- raise WalletBackendError(
5881
- "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
5952
+ approval_summary_copy = dict(approval_summary)
5953
+ approved_preview = args.get("_approved_preview")
5954
+ if isinstance(approval_summary_copy.get("_preview_digest"), str):
5955
+ if not isinstance(approved_preview, dict):
5956
+ raise WalletBackendError(
5957
+ "Approved Flash Trade preview payload is required for execute mode. Generate a new preview and approval before execute."
5958
+ )
5959
+ if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
5960
+ raise WalletBackendError(
5961
+ "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
5962
+ )
5963
+ execute_preview = dict(approved_preview)
5964
+ else:
5965
+ execute_preview = await active_backend.preview_flash_trade_open_position(
5966
+ pool_name=pool_name.strip(),
5967
+ market_symbol=market_symbol.strip(),
5968
+ collateral_symbol=collateral_symbol.strip(),
5969
+ collateral_amount_raw=collateral_amount_raw.strip(),
5970
+ leverage=leverage.strip(),
5971
+ side=side,
5882
5972
  )
5883
- execute_preview = dict(approved_preview)
5884
5973
  else:
5974
+ # No host token: build the same identity summary the
5975
+ # human path validates, and let _require_execute_approval's
5976
+ # autonomous_session / agentlayer_autonomous_approve
5977
+ # fallback authorize it.
5978
+ approval_summary_copy = {
5979
+ "operation": "Flash Trade open position",
5980
+ "pool_name": pool_name.strip(),
5981
+ "market_symbol": market_symbol.strip(),
5982
+ "collateral_symbol": collateral_symbol.strip(),
5983
+ "collateral_amount_raw": collateral_amount_raw.strip(),
5984
+ "leverage": leverage.strip(),
5985
+ "side": side,
5986
+ }
5885
5987
  execute_preview = await active_backend.preview_flash_trade_open_position(
5886
5988
  pool_name=pool_name.strip(),
5887
5989
  market_symbol=market_symbol.strip(),
@@ -5971,43 +6073,60 @@ class OpenClawWalletAdapter:
5971
6073
  ),
5972
6074
  )
5973
6075
 
5974
- approval_payload = inspect_approval_token(
5975
- approval_token,
5976
- tool_name=tool_name,
5977
- network=str(getattr(active_backend, "network", "unknown")),
5978
- require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
5979
- )
5980
- approval_summary = approval_payload.get("binding", {}).get("summary")
5981
- if not isinstance(approval_summary, dict):
5982
- raise WalletBackendError(
5983
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
6076
+ execute_preview = None
6077
+ if isinstance(approval_token, str) and approval_token.strip():
6078
+ approval_payload = inspect_approval_token(
6079
+ approval_token,
6080
+ tool_name=tool_name,
6081
+ network=str(getattr(active_backend, "network", "unknown")),
6082
+ require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
5984
6083
  )
5985
- expected_summary = {
5986
- "operation": "Flash Trade close position",
5987
- "pool_name": pool_name.strip(),
5988
- "market_symbol": market_symbol.strip(),
5989
- "side": side,
5990
- }
5991
- for key, expected_value in expected_summary.items():
5992
- if approval_summary.get(key) != expected_value:
6084
+ approval_summary = approval_payload.get("binding", {}).get("summary")
6085
+ if not isinstance(approval_summary, dict):
5993
6086
  raise WalletBackendError(
5994
6087
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5995
6088
  )
6089
+ expected_summary = {
6090
+ "operation": "Flash Trade close position",
6091
+ "pool_name": pool_name.strip(),
6092
+ "market_symbol": market_symbol.strip(),
6093
+ "side": side,
6094
+ }
6095
+ for key, expected_value in expected_summary.items():
6096
+ if approval_summary.get(key) != expected_value:
6097
+ raise WalletBackendError(
6098
+ "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
6099
+ )
5996
6100
 
5997
- approval_summary_copy = dict(approval_summary)
5998
- approved_preview = args.get("_approved_preview")
5999
- execute_preview = None
6000
- if isinstance(approval_summary_copy.get("_preview_digest"), str):
6001
- if not isinstance(approved_preview, dict):
6002
- raise WalletBackendError(
6003
- "Approved Flash Trade preview payload is required for execute mode. Generate a new preview and approval before execute."
6004
- )
6005
- if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
6006
- raise WalletBackendError(
6007
- "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
6101
+ approval_summary_copy = dict(approval_summary)
6102
+ approved_preview = args.get("_approved_preview")
6103
+ if isinstance(approval_summary_copy.get("_preview_digest"), str):
6104
+ if not isinstance(approved_preview, dict):
6105
+ raise WalletBackendError(
6106
+ "Approved Flash Trade preview payload is required for execute mode. Generate a new preview and approval before execute."
6107
+ )
6108
+ if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
6109
+ raise WalletBackendError(
6110
+ "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
6111
+ )
6112
+ execute_preview = dict(approved_preview)
6113
+ else:
6114
+ execute_preview = await active_backend.preview_flash_trade_close_position(
6115
+ pool_name=pool_name.strip(),
6116
+ market_symbol=market_symbol.strip(),
6117
+ side=side,
6008
6118
  )
6009
- execute_preview = dict(approved_preview)
6010
6119
  else:
6120
+ # No host token: build the same identity summary the
6121
+ # human path validates, and let _require_execute_approval's
6122
+ # autonomous_session / agentlayer_autonomous_approve
6123
+ # fallback authorize it.
6124
+ approval_summary_copy = {
6125
+ "operation": "Flash Trade close position",
6126
+ "pool_name": pool_name.strip(),
6127
+ "market_symbol": market_symbol.strip(),
6128
+ "side": side,
6129
+ }
6011
6130
  execute_preview = await active_backend.preview_flash_trade_close_position(
6012
6131
  pool_name=pool_name.strip(),
6013
6132
  market_symbol=market_symbol.strip(),
@@ -6524,44 +6643,60 @@ class OpenClawWalletAdapter:
6524
6643
  )
6525
6644
 
6526
6645
  if mode == "intent_execute":
6527
- approval_payload = inspect_approval_token(
6528
- approval_token,
6529
- tool_name=tool_name,
6530
- network=str(getattr(self.backend, "network", "unknown")),
6531
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6532
- )
6533
- approval_summary = approval_payload.get("binding", {}).get("summary")
6534
- if not isinstance(approval_summary, dict):
6535
- raise WalletBackendError(
6536
- "approval_token does not match the requested operation. Generate a new intent preview and approval before execute."
6646
+ if isinstance(approval_token, str) and approval_token.strip():
6647
+ approval_payload = inspect_approval_token(
6648
+ approval_token,
6649
+ tool_name=tool_name,
6650
+ network=str(getattr(self.backend, "network", "unknown")),
6651
+ require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6537
6652
  )
6538
- expected_summary = {
6539
- "operation": intent_action_label,
6540
- "network": str(getattr(self.backend, "network", "unknown")),
6541
- "kvault": kvault.strip(),
6542
- "amount_ui": amount_ui.strip(),
6543
- }
6544
- for key, expected_value in expected_summary.items():
6545
- if approval_summary.get(key) != expected_value:
6653
+ approval_summary = approval_payload.get("binding", {}).get("summary")
6654
+ if not isinstance(approval_summary, dict):
6546
6655
  raise WalletBackendError(
6547
- f"approval_token does not match the requested {action_label} intent. Generate a fresh intent preview and approval before execute."
6656
+ "approval_token does not match the requested operation. Generate a new intent preview and approval before execute."
6548
6657
  )
6549
- if approval_summary.get("recipient_policy") != "owner-only":
6550
- raise WalletBackendError("approved Kamino intent recipient policy is invalid.")
6551
- if approval_summary.get("spend_policy") != "exact-amount":
6552
- raise WalletBackendError("approved Kamino intent spend policy is invalid.")
6553
- current_owner = await self.backend.get_address()
6554
- approved_owner = approval_summary.get("owner")
6555
- if approved_owner and current_owner and str(approved_owner) != str(current_owner):
6556
- raise WalletBackendError(
6557
- "approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
6658
+ expected_summary = {
6659
+ "operation": intent_action_label,
6660
+ "network": str(getattr(self.backend, "network", "unknown")),
6661
+ "kvault": kvault.strip(),
6662
+ "amount_ui": amount_ui.strip(),
6663
+ }
6664
+ for key, expected_value in expected_summary.items():
6665
+ if approval_summary.get(key) != expected_value:
6666
+ raise WalletBackendError(
6667
+ f"approval_token does not match the requested {action_label} intent. Generate a fresh intent preview and approval before execute."
6668
+ )
6669
+ if approval_summary.get("recipient_policy") != "owner-only":
6670
+ raise WalletBackendError("approved Kamino intent recipient policy is invalid.")
6671
+ if approval_summary.get("spend_policy") != "exact-amount":
6672
+ raise WalletBackendError("approved Kamino intent spend policy is invalid.")
6673
+ current_owner = await self.backend.get_address()
6674
+ approved_owner = approval_summary.get("owner")
6675
+ if approved_owner and current_owner and str(approved_owner) != str(current_owner):
6676
+ raise WalletBackendError(
6677
+ "approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
6678
+ )
6679
+ valid_until = approval_summary.get("valid_until_epoch_seconds")
6680
+ if valid_until is not None and int(time.time()) > int(valid_until):
6681
+ raise WalletBackendError(
6682
+ "Approved Kamino intent has expired. Create a fresh intent preview."
6683
+ )
6684
+ approval_summary_copy = dict(approval_summary)
6685
+ else:
6686
+ # No host token: build the exact intent preview a
6687
+ # human would have seen (same call as
6688
+ # mode=intent_preview), and let
6689
+ # _require_execute_approval's autonomous_session /
6690
+ # agentlayer_autonomous_approve fallback authorize it.
6691
+ intent_preview = await intent_preview_method(
6692
+ kvault=kvault.strip(),
6693
+ amount_ui=amount_ui.strip(),
6694
+ valid_for_seconds=valid_for_seconds,
6558
6695
  )
6559
- valid_until = approval_summary.get("valid_until_epoch_seconds")
6560
- if valid_until is not None and int(time.time()) > int(valid_until):
6561
- raise WalletBackendError(
6562
- "Approved Kamino intent has expired. Create a fresh intent preview."
6696
+ approval_summary_copy = self._build_confirmation_summary(
6697
+ action_label=intent_action_label,
6698
+ payload=intent_preview,
6563
6699
  )
6564
- approval_summary_copy = dict(approval_summary)
6565
6700
  self._require_execute_approval(
6566
6701
  approval_token=approval_token,
6567
6702
  tool_name=tool_name,
@@ -6617,35 +6752,46 @@ class OpenClawWalletAdapter:
6617
6752
  ),
6618
6753
  )
6619
6754
 
6620
- approved_preview = args.get("_approved_preview")
6621
6755
  execute_preview = None
6622
- approval_payload = inspect_approval_token(
6623
- approval_token,
6624
- tool_name=tool_name,
6625
- network=str(getattr(self.backend, "network", "unknown")),
6626
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6627
- )
6628
- approval_summary = approval_payload.get("binding", {}).get("summary")
6629
- if not isinstance(approval_summary, dict):
6630
- raise WalletBackendError(
6631
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
6756
+ if isinstance(approval_token, str) and approval_token.strip():
6757
+ approved_preview = args.get("_approved_preview")
6758
+ approval_payload = inspect_approval_token(
6759
+ approval_token,
6760
+ tool_name=tool_name,
6761
+ network=str(getattr(self.backend, "network", "unknown")),
6762
+ require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6632
6763
  )
6633
- approval_summary_copy = dict(approval_summary)
6634
- if isinstance(approval_summary_copy.get("_preview_digest"), str):
6635
- if not isinstance(approved_preview, dict):
6764
+ approval_summary = approval_payload.get("binding", {}).get("summary")
6765
+ if not isinstance(approval_summary, dict):
6636
6766
  raise WalletBackendError(
6637
- f"Approved {action_label} preview payload is required for execute mode. Generate a new preview and approval before execute."
6767
+ "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
6638
6768
  )
6639
- if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
6640
- raise WalletBackendError(
6641
- "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
6769
+ approval_summary_copy = dict(approval_summary)
6770
+ if isinstance(approval_summary_copy.get("_preview_digest"), str):
6771
+ if not isinstance(approved_preview, dict):
6772
+ raise WalletBackendError(
6773
+ f"Approved {action_label} preview payload is required for execute mode. Generate a new preview and approval before execute."
6774
+ )
6775
+ if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
6776
+ raise WalletBackendError(
6777
+ "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
6778
+ )
6779
+ execute_preview = dict(approved_preview)
6780
+ else:
6781
+ execute_preview = await preview_method(
6782
+ kvault=kvault.strip(),
6783
+ amount_ui=amount_ui.strip(),
6642
6784
  )
6643
- execute_preview = dict(approved_preview)
6644
6785
  else:
6786
+ # No host token: same fallback as intent_execute above.
6645
6787
  execute_preview = await preview_method(
6646
6788
  kvault=kvault.strip(),
6647
6789
  amount_ui=amount_ui.strip(),
6648
6790
  )
6791
+ approval_summary_copy = self._build_confirmation_summary(
6792
+ action_label=action_label,
6793
+ payload=execute_preview,
6794
+ )
6649
6795
  self._require_execute_approval(
6650
6796
  approval_token=approval_token,
6651
6797
  tool_name=tool_name,
@@ -6768,60 +6914,83 @@ class OpenClawWalletAdapter:
6768
6914
  )
6769
6915
 
6770
6916
  if mode == "intent_execute":
6771
- approval_payload = inspect_approval_token(
6772
- approval_token,
6773
- tool_name=tool_name,
6774
- network=str(getattr(self.backend, "network", "unknown")),
6775
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6776
- )
6777
- approval_summary = approval_payload.get("binding", {}).get("summary")
6778
- if not isinstance(approval_summary, dict):
6779
- raise WalletBackendError(
6780
- "approval_token does not match the requested operation. Generate a new intent preview and approval before execute."
6917
+ if isinstance(approval_token, str) and approval_token.strip():
6918
+ approval_payload = inspect_approval_token(
6919
+ approval_token,
6920
+ tool_name=tool_name,
6921
+ network=str(getattr(self.backend, "network", "unknown")),
6922
+ require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6781
6923
  )
6782
- expected_summary = {
6783
- "operation": intent_action_label,
6784
- "network": str(getattr(self.backend, "network", "unknown")),
6785
- "market": market.strip(),
6786
- "reserve": reserve.strip(),
6787
- "amount_ui": amount_ui.strip(),
6788
- }
6789
- for key, expected_value in expected_summary.items():
6790
- if approval_summary.get(key) != expected_value:
6924
+ approval_summary = approval_payload.get("binding", {}).get("summary")
6925
+ if not isinstance(approval_summary, dict):
6791
6926
  raise WalletBackendError(
6792
- f"approval_token does not match the requested {action_label} intent. Generate a fresh intent preview and approval before execute."
6927
+ "approval_token does not match the requested operation. Generate a new intent preview and approval before execute."
6793
6928
  )
6794
- if approval_summary.get("recipient_policy") != "owner-only":
6795
- raise WalletBackendError("approved Kamino intent recipient policy is invalid.")
6796
- if approval_summary.get("spend_policy") != "exact-amount":
6797
- raise WalletBackendError("approved Kamino intent spend policy is invalid.")
6798
- current_owner = await self.backend.get_address()
6799
- approved_owner = approval_summary.get("owner")
6800
- if approved_owner and current_owner and str(approved_owner) != str(current_owner):
6801
- raise WalletBackendError(
6802
- "approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
6803
- )
6804
- approved_obligation = approval_summary.get("obligation_address")
6805
- if (
6806
- normalized_obligation_address is not None
6807
- and approved_obligation is not None
6808
- and str(approved_obligation) != normalized_obligation_address
6809
- ):
6810
- raise WalletBackendError(
6811
- "approval_token does not match the requested obligation. Generate a fresh intent preview and approval before execute."
6929
+ expected_summary = {
6930
+ "operation": intent_action_label,
6931
+ "network": str(getattr(self.backend, "network", "unknown")),
6932
+ "market": market.strip(),
6933
+ "reserve": reserve.strip(),
6934
+ "amount_ui": amount_ui.strip(),
6935
+ }
6936
+ for key, expected_value in expected_summary.items():
6937
+ if approval_summary.get(key) != expected_value:
6938
+ raise WalletBackendError(
6939
+ f"approval_token does not match the requested {action_label} intent. Generate a fresh intent preview and approval before execute."
6940
+ )
6941
+ if approval_summary.get("recipient_policy") != "owner-only":
6942
+ raise WalletBackendError("approved Kamino intent recipient policy is invalid.")
6943
+ if approval_summary.get("spend_policy") != "exact-amount":
6944
+ raise WalletBackendError("approved Kamino intent spend policy is invalid.")
6945
+ current_owner = await self.backend.get_address()
6946
+ approved_owner = approval_summary.get("owner")
6947
+ if approved_owner and current_owner and str(approved_owner) != str(current_owner):
6948
+ raise WalletBackendError(
6949
+ "approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
6950
+ )
6951
+ approved_obligation = approval_summary.get("obligation_address")
6952
+ if (
6953
+ normalized_obligation_address is not None
6954
+ and approved_obligation is not None
6955
+ and str(approved_obligation) != normalized_obligation_address
6956
+ ):
6957
+ raise WalletBackendError(
6958
+ "approval_token does not match the requested obligation. Generate a fresh intent preview and approval before execute."
6959
+ )
6960
+ valid_until = approval_summary.get("valid_until_epoch_seconds")
6961
+ if valid_until is not None and int(time.time()) > int(valid_until):
6962
+ raise WalletBackendError(
6963
+ "Approved Kamino intent has expired. Create a fresh intent preview."
6964
+ )
6965
+ approval_summary_copy = dict(approval_summary)
6966
+ else:
6967
+ # No host token: build the exact intent preview a
6968
+ # human would have seen (same call as
6969
+ # mode=intent_preview), and let
6970
+ # _require_execute_approval's autonomous_session /
6971
+ # agentlayer_autonomous_approve fallback authorize it.
6972
+ intent_preview = await intent_preview_method(
6973
+ market=market.strip(),
6974
+ reserve=reserve.strip(),
6975
+ amount_ui=amount_ui.strip(),
6976
+ obligation_address=normalized_obligation_address,
6977
+ valid_for_seconds=valid_for_seconds,
6812
6978
  )
6813
- valid_until = approval_summary.get("valid_until_epoch_seconds")
6814
- if valid_until is not None and int(time.time()) > int(valid_until):
6815
- raise WalletBackendError(
6816
- "Approved Kamino intent has expired. Create a fresh intent preview."
6979
+ if bool(intent_preview.get("requires_obligation_address")):
6980
+ raise WalletBackendError(
6981
+ f"{action_label} requires obligation_address when multiple Kamino obligations match the selected position."
6982
+ )
6983
+ approval_summary_copy = self._build_confirmation_summary(
6984
+ action_label=intent_action_label,
6985
+ payload=intent_preview,
6817
6986
  )
6818
- approval_summary_copy = dict(approval_summary)
6819
6987
  self._require_execute_approval(
6820
6988
  approval_token=approval_token,
6821
6989
  tool_name=tool_name,
6822
6990
  summary=approval_summary_copy,
6823
6991
  action_label=intent_action_label,
6824
6992
  )
6993
+ approved_obligation = approval_summary_copy.get("obligation_address")
6825
6994
  result = await execute_method(
6826
6995
  market=market.strip(),
6827
6996
  reserve=reserve.strip(),
@@ -6885,37 +7054,50 @@ class OpenClawWalletAdapter:
6885
7054
  ),
6886
7055
  )
6887
7056
 
6888
- approved_preview = args.get("_approved_preview")
6889
7057
  execute_preview = None
6890
- approval_payload = inspect_approval_token(
6891
- approval_token,
6892
- tool_name=tool_name,
6893
- network=str(getattr(self.backend, "network", "unknown")),
6894
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6895
- )
6896
- approval_summary = approval_payload.get("binding", {}).get("summary")
6897
- if not isinstance(approval_summary, dict):
6898
- raise WalletBackendError(
6899
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7058
+ if isinstance(approval_token, str) and approval_token.strip():
7059
+ approved_preview = args.get("_approved_preview")
7060
+ approval_payload = inspect_approval_token(
7061
+ approval_token,
7062
+ tool_name=tool_name,
7063
+ network=str(getattr(self.backend, "network", "unknown")),
7064
+ require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6900
7065
  )
6901
- approval_summary_copy = dict(approval_summary)
6902
- if isinstance(approval_summary_copy.get("_preview_digest"), str):
6903
- if not isinstance(approved_preview, dict):
7066
+ approval_summary = approval_payload.get("binding", {}).get("summary")
7067
+ if not isinstance(approval_summary, dict):
6904
7068
  raise WalletBackendError(
6905
- f"Approved {action_label} preview payload is required for execute mode. Generate a new preview and approval before execute."
7069
+ "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
6906
7070
  )
6907
- if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
6908
- raise WalletBackendError(
6909
- "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
7071
+ approval_summary_copy = dict(approval_summary)
7072
+ if isinstance(approval_summary_copy.get("_preview_digest"), str):
7073
+ if not isinstance(approved_preview, dict):
7074
+ raise WalletBackendError(
7075
+ f"Approved {action_label} preview payload is required for execute mode. Generate a new preview and approval before execute."
7076
+ )
7077
+ if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
7078
+ raise WalletBackendError(
7079
+ "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
7080
+ )
7081
+ execute_preview = dict(approved_preview)
7082
+ else:
7083
+ execute_preview = await preview_method(
7084
+ market=market.strip(),
7085
+ reserve=reserve.strip(),
7086
+ amount_ui=amount_ui.strip(),
7087
+ obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
6910
7088
  )
6911
- execute_preview = dict(approved_preview)
6912
7089
  else:
7090
+ # No host token: same fallback as intent_execute above.
6913
7091
  execute_preview = await preview_method(
6914
7092
  market=market.strip(),
6915
7093
  reserve=reserve.strip(),
6916
7094
  amount_ui=amount_ui.strip(),
6917
7095
  obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
6918
7096
  )
7097
+ approval_summary_copy = self._build_confirmation_summary(
7098
+ action_label=action_label,
7099
+ payload=execute_preview,
7100
+ )
6919
7101
  self._require_execute_approval(
6920
7102
  approval_token=approval_token,
6921
7103
  tool_name=tool_name,
@@ -7215,55 +7397,75 @@ class OpenClawWalletAdapter:
7215
7397
  )
7216
7398
 
7217
7399
  if mode == "intent_execute":
7218
- approval_payload = inspect_approval_token(
7219
- approval_token,
7220
- tool_name=tool_name,
7221
- network=str(getattr(self.backend, "network", "unknown")),
7222
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
7223
- )
7224
- approval_summary = approval_payload.get("binding", {}).get("summary")
7225
- if not isinstance(approval_summary, dict):
7226
- raise WalletBackendError(
7227
- "approval_token does not match the requested operation. Generate a new intent preview and approval before execute."
7400
+ if isinstance(approval_token, str) and approval_token.strip():
7401
+ approval_payload = inspect_approval_token(
7402
+ approval_token,
7403
+ tool_name=tool_name,
7404
+ network=str(getattr(self.backend, "network", "unknown")),
7405
+ require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
7228
7406
  )
7229
- expected_summary = {
7230
- "operation": "Swap intent",
7231
- "network": str(getattr(self.backend, "network", "unknown")),
7232
- "input_mint": input_mint.strip(),
7233
- "output_mint": output_mint.strip(),
7234
- }
7235
- for key, expected_value in expected_summary.items():
7236
- if approval_summary.get(key) != expected_value:
7407
+ approval_summary = approval_payload.get("binding", {}).get("summary")
7408
+ if not isinstance(approval_summary, dict):
7409
+ raise WalletBackendError(
7410
+ "approval_token does not match the requested operation. Generate a new intent preview and approval before execute."
7411
+ )
7412
+ expected_summary = {
7413
+ "operation": "Swap intent",
7414
+ "network": str(getattr(self.backend, "network", "unknown")),
7415
+ "input_mint": input_mint.strip(),
7416
+ "output_mint": output_mint.strip(),
7417
+ }
7418
+ for key, expected_value in expected_summary.items():
7419
+ if approval_summary.get(key) != expected_value:
7420
+ raise WalletBackendError(
7421
+ "approval_token does not match the requested swap intent. Generate a fresh intent preview and approval before execute."
7422
+ )
7423
+ current_owner = await self.backend.get_address()
7424
+ approved_owner = approval_summary.get("owner")
7425
+ if approved_owner and current_owner and str(approved_owner) != str(current_owner):
7426
+ raise WalletBackendError(
7427
+ "approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
7428
+ )
7429
+ try:
7430
+ approved_amount = float(approval_summary.get("input_amount_ui"))
7431
+ approved_slippage = int(
7432
+ approval_summary.get("max_slippage_bps")
7433
+ if approval_summary.get("max_slippage_bps") is not None
7434
+ else approval_summary.get("slippage_bps")
7435
+ )
7436
+ except (TypeError, ValueError):
7237
7437
  raise WalletBackendError(
7238
7438
  "approval_token does not match the requested swap intent. Generate a fresh intent preview and approval before execute."
7239
7439
  )
7240
- current_owner = await self.backend.get_address()
7241
- approved_owner = approval_summary.get("owner")
7242
- if approved_owner and current_owner and str(approved_owner) != str(current_owner):
7243
- raise WalletBackendError(
7244
- "approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
7245
- )
7246
- try:
7247
- approved_amount = float(approval_summary.get("input_amount_ui"))
7248
- approved_slippage = int(
7249
- approval_summary.get("max_slippage_bps")
7250
- if approval_summary.get("max_slippage_bps") is not None
7251
- else approval_summary.get("slippage_bps")
7252
- )
7253
- except (TypeError, ValueError):
7254
- raise WalletBackendError(
7255
- "approval_token does not match the requested swap intent. Generate a fresh intent preview and approval before execute."
7440
+ if approved_amount != float(amount) or approved_slippage != slippage_bps:
7441
+ raise WalletBackendError(
7442
+ "approval_token does not match the requested swap intent. Generate a fresh intent preview and approval before execute."
7443
+ )
7444
+ if approval_summary.get("recipient_policy") != "owner-only":
7445
+ raise WalletBackendError("approved swap intent recipient policy is invalid.")
7446
+ if approval_summary.get("spend_policy") != "exact-input":
7447
+ raise WalletBackendError("approved swap intent spend policy is invalid.")
7448
+
7449
+ approval_summary_copy = dict(approval_summary)
7450
+ else:
7451
+ # No host token: build the exact intent preview a human
7452
+ # would have seen (same call as mode=intent_preview),
7453
+ # and let _require_execute_approval's autonomous_session /
7454
+ # agentlayer_autonomous_approve fallback authorize it.
7455
+ intent_preview = await self.backend.preview_swap_intent(
7456
+ input_mint=input_mint.strip(),
7457
+ output_mint=output_mint.strip(),
7458
+ amount_ui=float(amount),
7459
+ slippage_bps=slippage_bps,
7460
+ minimum_output_amount_raw=minimum_output_amount_raw,
7461
+ max_fee_lamports=max_fee_lamports,
7462
+ valid_for_seconds=valid_for_seconds,
7463
+ max_attempts=max_attempts,
7256
7464
  )
7257
- if approved_amount != float(amount) or approved_slippage != slippage_bps:
7258
- raise WalletBackendError(
7259
- "approval_token does not match the requested swap intent. Generate a fresh intent preview and approval before execute."
7465
+ approval_summary_copy = self._build_confirmation_summary(
7466
+ action_label="Swap intent",
7467
+ payload=intent_preview,
7260
7468
  )
7261
- if approval_summary.get("recipient_policy") != "owner-only":
7262
- raise WalletBackendError("approved swap intent recipient policy is invalid.")
7263
- if approval_summary.get("spend_policy") != "exact-input":
7264
- raise WalletBackendError("approved swap intent spend policy is invalid.")
7265
-
7266
- approval_summary_copy = dict(approval_summary)
7267
7469
  self._require_execute_approval(
7268
7470
  approval_token=approval_token,
7269
7471
  tool_name=tool_name,
@@ -7311,59 +7513,75 @@ class OpenClawWalletAdapter:
7311
7513
  ),
7312
7514
  )
7313
7515
 
7314
- approval_payload = inspect_approval_token(
7315
- approval_token,
7316
- tool_name=tool_name,
7317
- network=str(getattr(self.backend, "network", "unknown")),
7318
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
7319
- )
7320
- approval_summary = approval_payload.get("binding", {}).get("summary")
7321
- if not isinstance(approval_summary, dict):
7322
- raise WalletBackendError(
7323
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7516
+ if isinstance(approval_token, str) and approval_token.strip():
7517
+ approval_payload = inspect_approval_token(
7518
+ approval_token,
7519
+ tool_name=tool_name,
7520
+ network=str(getattr(self.backend, "network", "unknown")),
7521
+ require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
7324
7522
  )
7325
- expected_summary = {
7326
- "operation": "Swap",
7327
- "network": str(getattr(self.backend, "network", "unknown")),
7328
- "input_mint": input_mint.strip(),
7329
- "output_mint": output_mint.strip(),
7330
- "slippage_bps": slippage_bps,
7331
- }
7332
- for key, expected_value in expected_summary.items():
7333
- if approval_summary.get(key) != expected_value:
7523
+ approval_summary = approval_payload.get("binding", {}).get("summary")
7524
+ if not isinstance(approval_summary, dict):
7334
7525
  raise WalletBackendError(
7335
7526
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7336
7527
  )
7337
- try:
7338
- approved_amount = float(approval_summary.get("input_amount_ui"))
7339
- except (TypeError, ValueError):
7340
- raise WalletBackendError(
7341
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7342
- )
7343
- if approved_amount != float(amount):
7344
- raise WalletBackendError(
7345
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7346
- )
7347
-
7348
- approval_summary_copy = dict(approval_summary)
7349
- approved_preview = args.get("_approved_preview")
7350
- if isinstance(approval_summary_copy.get("_preview_digest"), str):
7351
- if not isinstance(approved_preview, dict):
7528
+ expected_summary = {
7529
+ "operation": "Swap",
7530
+ "network": str(getattr(self.backend, "network", "unknown")),
7531
+ "input_mint": input_mint.strip(),
7532
+ "output_mint": output_mint.strip(),
7533
+ "slippage_bps": slippage_bps,
7534
+ }
7535
+ for key, expected_value in expected_summary.items():
7536
+ if approval_summary.get(key) != expected_value:
7537
+ raise WalletBackendError(
7538
+ "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7539
+ )
7540
+ try:
7541
+ approved_amount = float(approval_summary.get("input_amount_ui"))
7542
+ except (TypeError, ValueError):
7352
7543
  raise WalletBackendError(
7353
- "Approved swap preview payload is required for execute mode. Generate a new preview and approval before execute."
7544
+ "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7354
7545
  )
7355
- if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
7546
+ if approved_amount != float(amount):
7356
7547
  raise WalletBackendError(
7357
- "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
7548
+ "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7549
+ )
7550
+
7551
+ approval_summary_copy = dict(approval_summary)
7552
+ approved_preview = args.get("_approved_preview")
7553
+ if isinstance(approval_summary_copy.get("_preview_digest"), str):
7554
+ if not isinstance(approved_preview, dict):
7555
+ raise WalletBackendError(
7556
+ "Approved swap preview payload is required for execute mode. Generate a new preview and approval before execute."
7557
+ )
7558
+ if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
7559
+ raise WalletBackendError(
7560
+ "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
7561
+ )
7562
+ execute_preview = dict(approved_preview)
7563
+ else:
7564
+ execute_preview = await self.backend.preview_swap(
7565
+ input_mint=input_mint.strip(),
7566
+ output_mint=output_mint.strip(),
7567
+ amount_ui=float(amount),
7568
+ slippage_bps=slippage_bps,
7358
7569
  )
7359
- execute_preview = dict(approved_preview)
7360
7570
  else:
7571
+ # No host token: same fallback as intent_execute above --
7572
+ # build a fresh preview and let _require_execute_approval's
7573
+ # autonomous_session / agentlayer_autonomous_approve
7574
+ # fallback authorize it.
7361
7575
  execute_preview = await self.backend.preview_swap(
7362
7576
  input_mint=input_mint.strip(),
7363
7577
  output_mint=output_mint.strip(),
7364
7578
  amount_ui=float(amount),
7365
7579
  slippage_bps=slippage_bps,
7366
7580
  )
7581
+ approval_summary_copy = self._build_confirmation_summary(
7582
+ action_label="Swap",
7583
+ payload=execute_preview,
7584
+ )
7367
7585
  self._require_execute_approval(
7368
7586
  approval_token=approval_token,
7369
7587
  tool_name=tool_name,
@@ -7454,54 +7672,65 @@ class OpenClawWalletAdapter:
7454
7672
  ),
7455
7673
  )
7456
7674
 
7457
- approval_payload = inspect_approval_token(
7458
- approval_token,
7459
- tool_name=tool_name,
7460
- network=str(getattr(self.backend, "network", "unknown")),
7461
- require_mainnet_confirmation=self._is_mainnet(),
7462
- )
7463
- approval_summary = approval_payload.get("binding", {}).get("summary")
7464
- if not isinstance(approval_summary, dict):
7465
- raise WalletBackendError(
7466
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7675
+ if isinstance(approval_token, str) and approval_token.strip():
7676
+ approval_payload = inspect_approval_token(
7677
+ approval_token,
7678
+ tool_name=tool_name,
7679
+ network=str(getattr(self.backend, "network", "unknown")),
7680
+ require_mainnet_confirmation=self._is_mainnet(),
7467
7681
  )
7468
- destination_chain_id = self._canonicalize_lifi_chain_identifier(destination_chain)
7469
- expected_summary = {
7470
- "operation": "Solana LI.FI cross-chain swap",
7471
- "network": str(getattr(self.backend, "network", "unknown")),
7472
- "source_chain": "solana",
7473
- "destination_chain": destination_chain_id,
7474
- "input_token": self._canonicalize_lifi_token_identifier(
7475
- input_token,
7476
- chain_id="1151111081099710",
7477
- ),
7478
- "output_token": self._canonicalize_lifi_token_identifier(
7479
- output_token,
7480
- chain_id=destination_chain_id,
7481
- ),
7482
- "destination_address": destination_address.strip(),
7483
- "input_amount_raw": amount_in_raw.strip(),
7484
- }
7485
- for key, expected_value in expected_summary.items():
7486
- actual_value = approval_summary.get(key)
7487
- if key == "destination_chain":
7488
- actual_value = self._canonicalize_lifi_chain_identifier(actual_value)
7489
- if key == "input_token":
7490
- actual_value = self._canonicalize_lifi_token_identifier(
7491
- actual_value,
7492
- chain_id="1151111081099710",
7493
- )
7494
- if key == "output_token":
7495
- actual_value = self._canonicalize_lifi_token_identifier(
7496
- actual_value,
7497
- chain_id=destination_chain_id,
7498
- )
7499
- if actual_value != expected_value:
7682
+ approval_summary = approval_payload.get("binding", {}).get("summary")
7683
+ if not isinstance(approval_summary, dict):
7500
7684
  raise WalletBackendError(
7501
7685
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7502
7686
  )
7687
+ destination_chain_id = self._canonicalize_lifi_chain_identifier(destination_chain)
7688
+ expected_summary = {
7689
+ "operation": "Solana LI.FI cross-chain swap",
7690
+ "network": str(getattr(self.backend, "network", "unknown")),
7691
+ "source_chain": "solana",
7692
+ "destination_chain": destination_chain_id,
7693
+ "input_token": self._canonicalize_lifi_token_identifier(
7694
+ input_token,
7695
+ chain_id="1151111081099710",
7696
+ ),
7697
+ "output_token": self._canonicalize_lifi_token_identifier(
7698
+ output_token,
7699
+ chain_id=destination_chain_id,
7700
+ ),
7701
+ "destination_address": destination_address.strip(),
7702
+ "input_amount_raw": amount_in_raw.strip(),
7703
+ }
7704
+ for key, expected_value in expected_summary.items():
7705
+ actual_value = approval_summary.get(key)
7706
+ if key == "destination_chain":
7707
+ actual_value = self._canonicalize_lifi_chain_identifier(actual_value)
7708
+ if key == "input_token":
7709
+ actual_value = self._canonicalize_lifi_token_identifier(
7710
+ actual_value,
7711
+ chain_id="1151111081099710",
7712
+ )
7713
+ if key == "output_token":
7714
+ actual_value = self._canonicalize_lifi_token_identifier(
7715
+ actual_value,
7716
+ chain_id=destination_chain_id,
7717
+ )
7718
+ if actual_value != expected_value:
7719
+ raise WalletBackendError(
7720
+ "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7721
+ )
7503
7722
 
7504
- approval_summary_copy = dict(approval_summary)
7723
+ approval_summary_copy = dict(approval_summary)
7724
+ else:
7725
+ # No host token: build the exact preview a human would
7726
+ # have seen and let _require_execute_approval's
7727
+ # autonomous_session / agentlayer_autonomous_approve
7728
+ # fallback authorize it.
7729
+ fresh_preview = await self.backend.preview_solana_lifi_cross_chain_swap(**preview_kwargs)
7730
+ approval_summary_copy = self._build_confirmation_summary(
7731
+ action_label="Solana LI.FI cross-chain swap",
7732
+ payload=fresh_preview,
7733
+ )
7505
7734
  self._require_execute_approval(
7506
7735
  approval_token=approval_token,
7507
7736
  tool_name=tool_name,