@agentlayer.tech/wallet 0.1.88 → 0.1.90

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 (26) 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 +2 -1
  4. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  5. package/VERSION +1 -1
  6. package/agent-wallet/agent_wallet/__init__.py +1 -1
  7. package/agent-wallet/agent_wallet/autonomous_permissions.py +43 -1
  8. package/agent-wallet/agent_wallet/openclaw_adapter.py +696 -375
  9. package/agent-wallet/agent_wallet/providers/dexscreener.py +145 -0
  10. package/agent-wallet/agent_wallet/providers/x402.py +84 -0
  11. package/agent-wallet/agent_wallet/wallet_layer/base.py +12 -0
  12. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +38 -1
  13. package/agent-wallet/openclaw.plugin.json +1 -1
  14. package/agent-wallet/pyproject.toml +1 -1
  15. package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
  16. package/claude-code/plugins/agent-wallet/README.md +3 -1
  17. package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-approve.md +7 -8
  18. package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-revoke.md +2 -2
  19. package/claude-code/plugins/agent-wallet/commands/wallet-base.md +38 -15
  20. package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
  21. package/codex/plugins/agent-wallet/README.md +11 -6
  22. package/codex/plugins/agent-wallet/skills/wallet-base/SKILL.md +56 -0
  23. package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
  24. package/package.json +1 -1
  25. package/wdk-btc-wallet/package.json +1 -1
  26. 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
  )
@@ -2211,6 +2240,67 @@ class OpenClawWalletAdapter:
2211
2240
  risk_level="low",
2212
2241
  ),
2213
2242
  )
2243
+ tools.append(
2244
+ AgentToolSpec(
2245
+ name="search_uniswap_pairs",
2246
+ description=(
2247
+ "Search for onchain Uniswap-tradeable token pairs by ticker/name or by exact "
2248
+ "ERC-20 address, with live price, 5m/1h/6h/24h volume, liquidity, FDV, market "
2249
+ "cap, and buy/sell counts. Backed by DexScreener's public index (covers "
2250
+ "Uniswap v2/v3/v4 pools and other DEXes on the same chain), since the Uniswap "
2251
+ "Trading API itself only quotes a pair you already know the addresses for, not "
2252
+ "search-by-name. Defaults to the currently active EVM network (e.g. robinhood); "
2253
+ "set all_chains to search across every chain DexScreener indexes. Read-only "
2254
+ "market data, not a swap quote. Free-text search can surface impersonator "
2255
+ "tokens reusing a legitimate ticker with fabricated liquidity/FDV — before "
2256
+ "quoting or swapping, verify the token_address independently (e.g. via "
2257
+ "get_evm_token_metadata) rather than trusting the top result by symbol match. "
2258
+ "Do not use liquidity/FDV size to pick a winner: observed impersonators on "
2259
+ "robinhood fabricate liquidity/FDV an order of magnitude above the genuine "
2260
+ "pool specifically to look most trustworthy, and a Uniswap quote succeeding "
2261
+ "for a token proves nothing either (Trading API routes any pool with reserves, "
2262
+ "impersonators included). This only matters for tickers claiming to represent "
2263
+ "a real-world asset (stocks/ETFs) - unrelated meme/community tokens on the "
2264
+ "same chain aren't impersonators just for lacking a canonical issuer. When the "
2265
+ "query is a real-world-asset ticker, cross-check the returned token_address "
2266
+ "against Robinhood's own canonical list at docs.robinhood.com/chain/contracts "
2267
+ "(or the equivalent official source for other chains) before relying on the result."
2268
+ ),
2269
+ input_schema={
2270
+ "type": "object",
2271
+ "properties": {
2272
+ "query": {
2273
+ "type": "string",
2274
+ "description": "Free-text search, e.g. a ticker or name (\"AAPL\", \"Apple Robinhood\"). Required unless token_address is given.",
2275
+ },
2276
+ "token_address": {
2277
+ "type": "string",
2278
+ "description": "Exact ERC-20 contract address to look up instead of a free-text query.",
2279
+ },
2280
+ "chain": {
2281
+ "type": "string",
2282
+ "description": "DexScreener chain slug to filter to (e.g. \"robinhood\", \"ethereum\", \"base\"). Defaults to the active EVM network.",
2283
+ },
2284
+ "dex_id": {
2285
+ "type": "string",
2286
+ "description": "Optional exact DEX filter, e.g. \"uniswap\".",
2287
+ },
2288
+ "all_chains": {
2289
+ "type": "boolean",
2290
+ "description": "If true, do not filter query results to a single chain. Ignored when token_address is set.",
2291
+ },
2292
+ "limit": {
2293
+ "type": "integer",
2294
+ "description": "Max number of pairs to return (1-30). Defaults to 10.",
2295
+ },
2296
+ },
2297
+ "required": [],
2298
+ "additionalProperties": False,
2299
+ },
2300
+ read_only=True,
2301
+ risk_level="low",
2302
+ ),
2303
+ )
2214
2304
  tools.insert(
2215
2305
  12,
2216
2306
  AgentToolSpec(
@@ -3806,7 +3896,9 @@ class OpenClawWalletAdapter:
3806
3896
  name="agentlayer_autonomous_status",
3807
3897
  description=(
3808
3898
  "Return AgentLayer high-trust autonomous permission status. "
3809
- "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."
3810
3902
  ),
3811
3903
  input_schema={
3812
3904
  "type": "object",
@@ -3822,17 +3914,18 @@ class OpenClawWalletAdapter:
3822
3914
  name="agentlayer_autonomous_approve",
3823
3915
  description=(
3824
3916
  "Enable the high-trust autonomous permission group. The scope parameter is kept "
3825
- "for compatibility; choosing base_swaps or defi_tools enables both Base swaps and "
3826
- "supported EVM DeFi management tools until revoked. This does not cover transfers, "
3827
- "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."
3828
3921
  ),
3829
3922
  input_schema={
3830
3923
  "type": "object",
3831
3924
  "properties": {
3832
3925
  "scope": {
3833
3926
  "type": "string",
3834
- "enum": ["base_swaps", "defi_tools"],
3835
- "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.",
3836
3929
  },
3837
3930
  "purpose": {
3838
3931
  "type": "string",
@@ -3862,8 +3955,8 @@ class OpenClawWalletAdapter:
3862
3955
  "properties": {
3863
3956
  "scope": {
3864
3957
  "type": "string",
3865
- "enum": ["base_swaps", "defi_tools"],
3866
- "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.",
3867
3960
  }
3868
3961
  },
3869
3962
  "required": ["scope"],
@@ -3888,7 +3981,9 @@ class OpenClawWalletAdapter:
3888
3981
  name="agentlayer_autonomous_status",
3889
3982
  description=(
3890
3983
  "Return AgentLayer high-trust autonomous permission status. "
3891
- "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."
3892
3987
  ),
3893
3988
  input_schema={
3894
3989
  "type": "object",
@@ -3902,17 +3997,18 @@ class OpenClawWalletAdapter:
3902
3997
  name="agentlayer_autonomous_approve",
3903
3998
  description=(
3904
3999
  "Enable the high-trust autonomous permission group. The scope parameter is kept "
3905
- "for compatibility; choosing base_swaps or defi_tools enables both Base swaps and "
3906
- "supported EVM DeFi management tools until revoked. This does not cover transfers, "
3907
- "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."
3908
4004
  ),
3909
4005
  input_schema={
3910
4006
  "type": "object",
3911
4007
  "properties": {
3912
4008
  "scope": {
3913
4009
  "type": "string",
3914
- "enum": ["base_swaps", "defi_tools"],
3915
- "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.",
3916
4012
  },
3917
4013
  "purpose": {
3918
4014
  "type": "string",
@@ -3940,8 +4036,8 @@ class OpenClawWalletAdapter:
3940
4036
  "properties": {
3941
4037
  "scope": {
3942
4038
  "type": "string",
3943
- "enum": ["base_swaps", "defi_tools"],
3944
- "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.",
3945
4041
  }
3946
4042
  },
3947
4043
  "required": ["scope"],
@@ -3979,7 +4075,7 @@ class OpenClawWalletAdapter:
3979
4075
  scope = str(args.get("scope") or "").strip()
3980
4076
  purpose = args.get("purpose")
3981
4077
  if scope not in autonomous_permissions.SUPPORTED_SCOPES:
3982
- raise WalletBackendError("scope must be one of: base_swaps, defi_tools.")
4078
+ raise WalletBackendError("scope must be one of: all, base_swaps, defi_tools.")
3983
4079
  if not isinstance(purpose, str) or not purpose.strip():
3984
4080
  raise WalletBackendError("purpose is required.")
3985
4081
  if args.get("user_intent") is not True:
@@ -4000,7 +4096,7 @@ class OpenClawWalletAdapter:
4000
4096
 
4001
4097
  scope = str(args.get("scope") or "").strip()
4002
4098
  if scope not in autonomous_permissions.SUPPORTED_SCOPES:
4003
- raise WalletBackendError("scope must be one of: base_swaps, defi_tools.")
4099
+ raise WalletBackendError("scope must be one of: all, base_swaps, defi_tools.")
4004
4100
  return AgentToolResult(
4005
4101
  tool=tool_name,
4006
4102
  ok=True,
@@ -4145,7 +4241,14 @@ class OpenClawWalletAdapter:
4145
4241
  approved_preview = args.get("_approved_preview")
4146
4242
  if approved_preview is not None and not isinstance(approved_preview, dict):
4147
4243
  raise WalletBackendError("_approved_preview must be an object when provided.")
4148
- 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(
4149
4252
  backend=active_backend,
4150
4253
  url=url.strip(),
4151
4254
  method=method,
@@ -4155,6 +4258,32 @@ class OpenClawWalletAdapter:
4155
4258
  text_body=text_body,
4156
4259
  approved_preview=approved_preview,
4157
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
+ )
4158
4287
  data["purpose"] = purpose.strip()
4159
4288
  data = self._annotate_x402_payload(data, mode="execute")
4160
4289
  return AgentToolResult(tool=tool_name, ok=True, data=data)
@@ -5349,57 +5478,68 @@ class OpenClawWalletAdapter:
5349
5478
  ),
5350
5479
  )
5351
5480
 
5352
- approval_payload = inspect_approval_token(
5353
- approval_token,
5354
- tool_name=tool_name,
5355
- network=str(getattr(active_backend, "network", "unknown")),
5356
- require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
5357
- )
5358
- approval_summary = approval_payload.get("binding", {}).get("summary")
5359
- if not isinstance(approval_summary, dict):
5360
- raise WalletBackendError(
5361
- "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),
5362
5487
  )
5363
- source_chain_id = self._canonicalize_lifi_chain_identifier(
5364
- getattr(active_backend, "network", "unknown")
5365
- )
5366
- destination_chain_id = self._canonicalize_lifi_chain_identifier(destination_chain)
5367
- expected_summary = {
5368
- "operation": "EVM LI.FI cross-chain swap",
5369
- "network": str(getattr(active_backend, "network", "unknown")),
5370
- "source_chain": source_chain_id,
5371
- "destination_chain": destination_chain_id,
5372
- "token_in": self._canonicalize_lifi_token_identifier(
5373
- token_in,
5374
- chain_id=source_chain_id,
5375
- ),
5376
- "output_token": self._canonicalize_lifi_token_identifier(
5377
- output_token,
5378
- chain_id=destination_chain_id,
5379
- ),
5380
- "destination_address": destination_address.strip(),
5381
- "input_amount_raw": amount_in_raw.strip(),
5382
- }
5383
- for key, expected_value in expected_summary.items():
5384
- actual_value = approval_summary.get(key)
5385
- if key in {"source_chain", "destination_chain"}:
5386
- actual_value = self._canonicalize_lifi_chain_identifier(actual_value)
5387
- if key == "token_in":
5388
- actual_value = self._canonicalize_lifi_token_identifier(
5389
- actual_value,
5390
- chain_id=source_chain_id,
5391
- )
5392
- if key == "output_token":
5393
- actual_value = self._canonicalize_lifi_token_identifier(
5394
- actual_value,
5395
- chain_id=destination_chain_id,
5396
- )
5397
- if actual_value != expected_value:
5488
+ approval_summary = approval_payload.get("binding", {}).get("summary")
5489
+ if not isinstance(approval_summary, dict):
5398
5490
  raise WalletBackendError(
5399
5491
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5400
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
+ )
5401
5531
 
5402
- 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
+ )
5403
5543
  self._require_execute_approval(
5404
5544
  approval_token=approval_token,
5405
5545
  tool_name=tool_name,
@@ -5460,6 +5600,37 @@ class OpenClawWalletAdapter:
5460
5600
  )
5461
5601
  return AgentToolResult(tool=tool_name, ok=True, data=data)
5462
5602
 
5603
+ if tool_name == "search_uniswap_pairs":
5604
+ query = args.get("query")
5605
+ token_address = args.get("token_address")
5606
+ chain = args.get("chain")
5607
+ dex_id = args.get("dex_id")
5608
+ all_chains = bool(args.get("all_chains", False))
5609
+ limit = args.get("limit", 10)
5610
+ if query is not None and not isinstance(query, str):
5611
+ raise WalletBackendError("query must be a string.")
5612
+ if token_address is not None and not isinstance(token_address, str):
5613
+ raise WalletBackendError("token_address must be a string.")
5614
+ if not (isinstance(query, str) and query.strip()) and not (
5615
+ isinstance(token_address, str) and token_address.strip()
5616
+ ):
5617
+ raise WalletBackendError("Either query or token_address is required.")
5618
+ if chain is not None and not isinstance(chain, str):
5619
+ raise WalletBackendError("chain must be a string.")
5620
+ if dex_id is not None and not isinstance(dex_id, str):
5621
+ raise WalletBackendError("dex_id must be a string.")
5622
+ if not isinstance(limit, int) or limit < 1 or limit > 30:
5623
+ raise WalletBackendError("limit must be an integer between 1 and 30.")
5624
+ data = await active_backend.search_uniswap_pairs(
5625
+ query=query,
5626
+ token_address=token_address,
5627
+ chain=chain,
5628
+ dex_id=dex_id,
5629
+ all_chains=all_chains,
5630
+ limit=limit,
5631
+ )
5632
+ return AgentToolResult(tool=tool_name, ok=True, data=data)
5633
+
5463
5634
  if tool_name == "swap_evm_uniswap_tokens":
5464
5635
  token_in = args.get("token_in")
5465
5636
  token_out = args.get("token_out")
@@ -5750,46 +5921,69 @@ class OpenClawWalletAdapter:
5750
5921
  ),
5751
5922
  )
5752
5923
 
5753
- approval_payload = inspect_approval_token(
5754
- approval_token,
5755
- tool_name=tool_name,
5756
- network=str(getattr(active_backend, "network", "unknown")),
5757
- require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
5758
- )
5759
- approval_summary = approval_payload.get("binding", {}).get("summary")
5760
- if not isinstance(approval_summary, dict):
5761
- raise WalletBackendError(
5762
- "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),
5763
5931
  )
5764
- expected_summary = {
5765
- "operation": "Flash Trade open position",
5766
- "pool_name": pool_name.strip(),
5767
- "market_symbol": market_symbol.strip(),
5768
- "collateral_symbol": collateral_symbol.strip(),
5769
- "collateral_amount_raw": collateral_amount_raw.strip(),
5770
- "leverage": leverage.strip(),
5771
- "side": side,
5772
- }
5773
- for key, expected_value in expected_summary.items():
5774
- if approval_summary.get(key) != expected_value:
5932
+ approval_summary = approval_payload.get("binding", {}).get("summary")
5933
+ if not isinstance(approval_summary, dict):
5775
5934
  raise WalletBackendError(
5776
5935
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5777
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
+ )
5778
5951
 
5779
- approval_summary_copy = dict(approval_summary)
5780
- approved_preview = args.get("_approved_preview")
5781
- execute_preview = None
5782
- if isinstance(approval_summary_copy.get("_preview_digest"), str):
5783
- if not isinstance(approved_preview, dict):
5784
- raise WalletBackendError(
5785
- "Approved Flash Trade preview payload is required for execute mode. Generate a new preview and approval before execute."
5786
- )
5787
- if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
5788
- raise WalletBackendError(
5789
- "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,
5790
5972
  )
5791
- execute_preview = dict(approved_preview)
5792
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
+ }
5793
5987
  execute_preview = await active_backend.preview_flash_trade_open_position(
5794
5988
  pool_name=pool_name.strip(),
5795
5989
  market_symbol=market_symbol.strip(),
@@ -5879,43 +6073,60 @@ class OpenClawWalletAdapter:
5879
6073
  ),
5880
6074
  )
5881
6075
 
5882
- approval_payload = inspect_approval_token(
5883
- approval_token,
5884
- tool_name=tool_name,
5885
- network=str(getattr(active_backend, "network", "unknown")),
5886
- require_mainnet_confirmation=self._is_mainnet_for_backend(active_backend),
5887
- )
5888
- approval_summary = approval_payload.get("binding", {}).get("summary")
5889
- if not isinstance(approval_summary, dict):
5890
- raise WalletBackendError(
5891
- "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),
5892
6083
  )
5893
- expected_summary = {
5894
- "operation": "Flash Trade close position",
5895
- "pool_name": pool_name.strip(),
5896
- "market_symbol": market_symbol.strip(),
5897
- "side": side,
5898
- }
5899
- for key, expected_value in expected_summary.items():
5900
- if approval_summary.get(key) != expected_value:
6084
+ approval_summary = approval_payload.get("binding", {}).get("summary")
6085
+ if not isinstance(approval_summary, dict):
5901
6086
  raise WalletBackendError(
5902
6087
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5903
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
+ )
5904
6100
 
5905
- approval_summary_copy = dict(approval_summary)
5906
- approved_preview = args.get("_approved_preview")
5907
- execute_preview = None
5908
- if isinstance(approval_summary_copy.get("_preview_digest"), str):
5909
- if not isinstance(approved_preview, dict):
5910
- raise WalletBackendError(
5911
- "Approved Flash Trade preview payload is required for execute mode. Generate a new preview and approval before execute."
5912
- )
5913
- if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
5914
- raise WalletBackendError(
5915
- "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,
5916
6118
  )
5917
- execute_preview = dict(approved_preview)
5918
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
+ }
5919
6130
  execute_preview = await active_backend.preview_flash_trade_close_position(
5920
6131
  pool_name=pool_name.strip(),
5921
6132
  market_symbol=market_symbol.strip(),
@@ -6432,44 +6643,60 @@ class OpenClawWalletAdapter:
6432
6643
  )
6433
6644
 
6434
6645
  if mode == "intent_execute":
6435
- approval_payload = inspect_approval_token(
6436
- approval_token,
6437
- tool_name=tool_name,
6438
- network=str(getattr(self.backend, "network", "unknown")),
6439
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6440
- )
6441
- approval_summary = approval_payload.get("binding", {}).get("summary")
6442
- if not isinstance(approval_summary, dict):
6443
- raise WalletBackendError(
6444
- "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),
6445
6652
  )
6446
- expected_summary = {
6447
- "operation": intent_action_label,
6448
- "network": str(getattr(self.backend, "network", "unknown")),
6449
- "kvault": kvault.strip(),
6450
- "amount_ui": amount_ui.strip(),
6451
- }
6452
- for key, expected_value in expected_summary.items():
6453
- if approval_summary.get(key) != expected_value:
6653
+ approval_summary = approval_payload.get("binding", {}).get("summary")
6654
+ if not isinstance(approval_summary, dict):
6454
6655
  raise WalletBackendError(
6455
- 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."
6456
6657
  )
6457
- if approval_summary.get("recipient_policy") != "owner-only":
6458
- raise WalletBackendError("approved Kamino intent recipient policy is invalid.")
6459
- if approval_summary.get("spend_policy") != "exact-amount":
6460
- raise WalletBackendError("approved Kamino intent spend policy is invalid.")
6461
- current_owner = await self.backend.get_address()
6462
- approved_owner = approval_summary.get("owner")
6463
- if approved_owner and current_owner and str(approved_owner) != str(current_owner):
6464
- raise WalletBackendError(
6465
- "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,
6466
6695
  )
6467
- valid_until = approval_summary.get("valid_until_epoch_seconds")
6468
- if valid_until is not None and int(time.time()) > int(valid_until):
6469
- raise WalletBackendError(
6470
- "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,
6471
6699
  )
6472
- approval_summary_copy = dict(approval_summary)
6473
6700
  self._require_execute_approval(
6474
6701
  approval_token=approval_token,
6475
6702
  tool_name=tool_name,
@@ -6525,35 +6752,46 @@ class OpenClawWalletAdapter:
6525
6752
  ),
6526
6753
  )
6527
6754
 
6528
- approved_preview = args.get("_approved_preview")
6529
6755
  execute_preview = None
6530
- approval_payload = inspect_approval_token(
6531
- approval_token,
6532
- tool_name=tool_name,
6533
- network=str(getattr(self.backend, "network", "unknown")),
6534
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6535
- )
6536
- approval_summary = approval_payload.get("binding", {}).get("summary")
6537
- if not isinstance(approval_summary, dict):
6538
- raise WalletBackendError(
6539
- "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),
6540
6763
  )
6541
- approval_summary_copy = dict(approval_summary)
6542
- if isinstance(approval_summary_copy.get("_preview_digest"), str):
6543
- if not isinstance(approved_preview, dict):
6764
+ approval_summary = approval_payload.get("binding", {}).get("summary")
6765
+ if not isinstance(approval_summary, dict):
6544
6766
  raise WalletBackendError(
6545
- 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."
6546
6768
  )
6547
- if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
6548
- raise WalletBackendError(
6549
- "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(),
6550
6784
  )
6551
- execute_preview = dict(approved_preview)
6552
6785
  else:
6786
+ # No host token: same fallback as intent_execute above.
6553
6787
  execute_preview = await preview_method(
6554
6788
  kvault=kvault.strip(),
6555
6789
  amount_ui=amount_ui.strip(),
6556
6790
  )
6791
+ approval_summary_copy = self._build_confirmation_summary(
6792
+ action_label=action_label,
6793
+ payload=execute_preview,
6794
+ )
6557
6795
  self._require_execute_approval(
6558
6796
  approval_token=approval_token,
6559
6797
  tool_name=tool_name,
@@ -6676,60 +6914,83 @@ class OpenClawWalletAdapter:
6676
6914
  )
6677
6915
 
6678
6916
  if mode == "intent_execute":
6679
- approval_payload = inspect_approval_token(
6680
- approval_token,
6681
- tool_name=tool_name,
6682
- network=str(getattr(self.backend, "network", "unknown")),
6683
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6684
- )
6685
- approval_summary = approval_payload.get("binding", {}).get("summary")
6686
- if not isinstance(approval_summary, dict):
6687
- raise WalletBackendError(
6688
- "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),
6689
6923
  )
6690
- expected_summary = {
6691
- "operation": intent_action_label,
6692
- "network": str(getattr(self.backend, "network", "unknown")),
6693
- "market": market.strip(),
6694
- "reserve": reserve.strip(),
6695
- "amount_ui": amount_ui.strip(),
6696
- }
6697
- for key, expected_value in expected_summary.items():
6698
- if approval_summary.get(key) != expected_value:
6924
+ approval_summary = approval_payload.get("binding", {}).get("summary")
6925
+ if not isinstance(approval_summary, dict):
6699
6926
  raise WalletBackendError(
6700
- 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."
6701
6928
  )
6702
- if approval_summary.get("recipient_policy") != "owner-only":
6703
- raise WalletBackendError("approved Kamino intent recipient policy is invalid.")
6704
- if approval_summary.get("spend_policy") != "exact-amount":
6705
- raise WalletBackendError("approved Kamino intent spend policy is invalid.")
6706
- current_owner = await self.backend.get_address()
6707
- approved_owner = approval_summary.get("owner")
6708
- if approved_owner and current_owner and str(approved_owner) != str(current_owner):
6709
- raise WalletBackendError(
6710
- "approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
6711
- )
6712
- approved_obligation = approval_summary.get("obligation_address")
6713
- if (
6714
- normalized_obligation_address is not None
6715
- and approved_obligation is not None
6716
- and str(approved_obligation) != normalized_obligation_address
6717
- ):
6718
- raise WalletBackendError(
6719
- "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,
6720
6978
  )
6721
- valid_until = approval_summary.get("valid_until_epoch_seconds")
6722
- if valid_until is not None and int(time.time()) > int(valid_until):
6723
- raise WalletBackendError(
6724
- "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,
6725
6986
  )
6726
- approval_summary_copy = dict(approval_summary)
6727
6987
  self._require_execute_approval(
6728
6988
  approval_token=approval_token,
6729
6989
  tool_name=tool_name,
6730
6990
  summary=approval_summary_copy,
6731
6991
  action_label=intent_action_label,
6732
6992
  )
6993
+ approved_obligation = approval_summary_copy.get("obligation_address")
6733
6994
  result = await execute_method(
6734
6995
  market=market.strip(),
6735
6996
  reserve=reserve.strip(),
@@ -6793,37 +7054,50 @@ class OpenClawWalletAdapter:
6793
7054
  ),
6794
7055
  )
6795
7056
 
6796
- approved_preview = args.get("_approved_preview")
6797
7057
  execute_preview = None
6798
- approval_payload = inspect_approval_token(
6799
- approval_token,
6800
- tool_name=tool_name,
6801
- network=str(getattr(self.backend, "network", "unknown")),
6802
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
6803
- )
6804
- approval_summary = approval_payload.get("binding", {}).get("summary")
6805
- if not isinstance(approval_summary, dict):
6806
- raise WalletBackendError(
6807
- "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),
6808
7065
  )
6809
- approval_summary_copy = dict(approval_summary)
6810
- if isinstance(approval_summary_copy.get("_preview_digest"), str):
6811
- if not isinstance(approved_preview, dict):
7066
+ approval_summary = approval_payload.get("binding", {}).get("summary")
7067
+ if not isinstance(approval_summary, dict):
6812
7068
  raise WalletBackendError(
6813
- 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."
6814
7070
  )
6815
- if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
6816
- raise WalletBackendError(
6817
- "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,
6818
7088
  )
6819
- execute_preview = dict(approved_preview)
6820
7089
  else:
7090
+ # No host token: same fallback as intent_execute above.
6821
7091
  execute_preview = await preview_method(
6822
7092
  market=market.strip(),
6823
7093
  reserve=reserve.strip(),
6824
7094
  amount_ui=amount_ui.strip(),
6825
7095
  obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
6826
7096
  )
7097
+ approval_summary_copy = self._build_confirmation_summary(
7098
+ action_label=action_label,
7099
+ payload=execute_preview,
7100
+ )
6827
7101
  self._require_execute_approval(
6828
7102
  approval_token=approval_token,
6829
7103
  tool_name=tool_name,
@@ -7123,55 +7397,75 @@ class OpenClawWalletAdapter:
7123
7397
  )
7124
7398
 
7125
7399
  if mode == "intent_execute":
7126
- approval_payload = inspect_approval_token(
7127
- approval_token,
7128
- tool_name=tool_name,
7129
- network=str(getattr(self.backend, "network", "unknown")),
7130
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
7131
- )
7132
- approval_summary = approval_payload.get("binding", {}).get("summary")
7133
- if not isinstance(approval_summary, dict):
7134
- raise WalletBackendError(
7135
- "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),
7136
7406
  )
7137
- expected_summary = {
7138
- "operation": "Swap intent",
7139
- "network": str(getattr(self.backend, "network", "unknown")),
7140
- "input_mint": input_mint.strip(),
7141
- "output_mint": output_mint.strip(),
7142
- }
7143
- for key, expected_value in expected_summary.items():
7144
- 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):
7145
7437
  raise WalletBackendError(
7146
7438
  "approval_token does not match the requested swap intent. Generate a fresh intent preview and approval before execute."
7147
7439
  )
7148
- current_owner = await self.backend.get_address()
7149
- approved_owner = approval_summary.get("owner")
7150
- if approved_owner and current_owner and str(approved_owner) != str(current_owner):
7151
- raise WalletBackendError(
7152
- "approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
7153
- )
7154
- try:
7155
- approved_amount = float(approval_summary.get("input_amount_ui"))
7156
- approved_slippage = int(
7157
- approval_summary.get("max_slippage_bps")
7158
- if approval_summary.get("max_slippage_bps") is not None
7159
- else approval_summary.get("slippage_bps")
7160
- )
7161
- except (TypeError, ValueError):
7162
- raise WalletBackendError(
7163
- "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,
7164
7464
  )
7165
- if approved_amount != float(amount) or approved_slippage != slippage_bps:
7166
- raise WalletBackendError(
7167
- "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,
7168
7468
  )
7169
- if approval_summary.get("recipient_policy") != "owner-only":
7170
- raise WalletBackendError("approved swap intent recipient policy is invalid.")
7171
- if approval_summary.get("spend_policy") != "exact-input":
7172
- raise WalletBackendError("approved swap intent spend policy is invalid.")
7173
-
7174
- approval_summary_copy = dict(approval_summary)
7175
7469
  self._require_execute_approval(
7176
7470
  approval_token=approval_token,
7177
7471
  tool_name=tool_name,
@@ -7219,59 +7513,75 @@ class OpenClawWalletAdapter:
7219
7513
  ),
7220
7514
  )
7221
7515
 
7222
- approval_payload = inspect_approval_token(
7223
- approval_token,
7224
- tool_name=tool_name,
7225
- network=str(getattr(self.backend, "network", "unknown")),
7226
- require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
7227
- )
7228
- approval_summary = approval_payload.get("binding", {}).get("summary")
7229
- if not isinstance(approval_summary, dict):
7230
- raise WalletBackendError(
7231
- "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),
7232
7522
  )
7233
- expected_summary = {
7234
- "operation": "Swap",
7235
- "network": str(getattr(self.backend, "network", "unknown")),
7236
- "input_mint": input_mint.strip(),
7237
- "output_mint": output_mint.strip(),
7238
- "slippage_bps": slippage_bps,
7239
- }
7240
- for key, expected_value in expected_summary.items():
7241
- if approval_summary.get(key) != expected_value:
7523
+ approval_summary = approval_payload.get("binding", {}).get("summary")
7524
+ if not isinstance(approval_summary, dict):
7242
7525
  raise WalletBackendError(
7243
7526
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7244
7527
  )
7245
- try:
7246
- approved_amount = float(approval_summary.get("input_amount_ui"))
7247
- except (TypeError, ValueError):
7248
- raise WalletBackendError(
7249
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7250
- )
7251
- if approved_amount != float(amount):
7252
- raise WalletBackendError(
7253
- "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7254
- )
7255
-
7256
- approval_summary_copy = dict(approval_summary)
7257
- approved_preview = args.get("_approved_preview")
7258
- if isinstance(approval_summary_copy.get("_preview_digest"), str):
7259
- 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):
7260
7543
  raise WalletBackendError(
7261
- "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."
7262
7545
  )
7263
- if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
7546
+ if approved_amount != float(amount):
7264
7547
  raise WalletBackendError(
7265
- "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,
7266
7569
  )
7267
- execute_preview = dict(approved_preview)
7268
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.
7269
7575
  execute_preview = await self.backend.preview_swap(
7270
7576
  input_mint=input_mint.strip(),
7271
7577
  output_mint=output_mint.strip(),
7272
7578
  amount_ui=float(amount),
7273
7579
  slippage_bps=slippage_bps,
7274
7580
  )
7581
+ approval_summary_copy = self._build_confirmation_summary(
7582
+ action_label="Swap",
7583
+ payload=execute_preview,
7584
+ )
7275
7585
  self._require_execute_approval(
7276
7586
  approval_token=approval_token,
7277
7587
  tool_name=tool_name,
@@ -7362,54 +7672,65 @@ class OpenClawWalletAdapter:
7362
7672
  ),
7363
7673
  )
7364
7674
 
7365
- approval_payload = inspect_approval_token(
7366
- approval_token,
7367
- tool_name=tool_name,
7368
- network=str(getattr(self.backend, "network", "unknown")),
7369
- require_mainnet_confirmation=self._is_mainnet(),
7370
- )
7371
- approval_summary = approval_payload.get("binding", {}).get("summary")
7372
- if not isinstance(approval_summary, dict):
7373
- raise WalletBackendError(
7374
- "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(),
7375
7681
  )
7376
- destination_chain_id = self._canonicalize_lifi_chain_identifier(destination_chain)
7377
- expected_summary = {
7378
- "operation": "Solana LI.FI cross-chain swap",
7379
- "network": str(getattr(self.backend, "network", "unknown")),
7380
- "source_chain": "solana",
7381
- "destination_chain": destination_chain_id,
7382
- "input_token": self._canonicalize_lifi_token_identifier(
7383
- input_token,
7384
- chain_id="1151111081099710",
7385
- ),
7386
- "output_token": self._canonicalize_lifi_token_identifier(
7387
- output_token,
7388
- chain_id=destination_chain_id,
7389
- ),
7390
- "destination_address": destination_address.strip(),
7391
- "input_amount_raw": amount_in_raw.strip(),
7392
- }
7393
- for key, expected_value in expected_summary.items():
7394
- actual_value = approval_summary.get(key)
7395
- if key == "destination_chain":
7396
- actual_value = self._canonicalize_lifi_chain_identifier(actual_value)
7397
- if key == "input_token":
7398
- actual_value = self._canonicalize_lifi_token_identifier(
7399
- actual_value,
7400
- chain_id="1151111081099710",
7401
- )
7402
- if key == "output_token":
7403
- actual_value = self._canonicalize_lifi_token_identifier(
7404
- actual_value,
7405
- chain_id=destination_chain_id,
7406
- )
7407
- if actual_value != expected_value:
7682
+ approval_summary = approval_payload.get("binding", {}).get("summary")
7683
+ if not isinstance(approval_summary, dict):
7408
7684
  raise WalletBackendError(
7409
7685
  "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
7410
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
+ )
7411
7722
 
7412
- 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
+ )
7413
7734
  self._require_execute_approval(
7414
7735
  approval_token=approval_token,
7415
7736
  tool_name=tool_name,