@agentlayer.tech/wallet 0.1.24 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.openclaw/extensions/agent-wallet/README.md +3 -3
- package/.openclaw/extensions/agent-wallet/dist/index.js +138 -96
- package/.openclaw/extensions/agent-wallet/index.ts +138 -96
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +4 -0
- package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +4 -1
- package/CHANGELOG.md +14 -0
- package/agent-wallet/README.md +4 -3
- package/agent-wallet/agent_wallet/config.py +1 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +187 -73
- package/agent-wallet/agent_wallet/openclaw_cli.py +1 -0
- package/agent-wallet/agent_wallet/providers/jupiter.py +89 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +111 -5
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +298 -23
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_openclaw_local_config.py +59 -5
- package/agent-wallet/skills/wallet-operator/SKILL.md +10 -6
- package/package.json +1 -1
- package/wdk-evm-wallet/package-lock.json +123 -309
- package/wdk-evm-wallet/package.json +10 -3
|
@@ -222,6 +222,10 @@
|
|
|
222
222
|
"type": "string",
|
|
223
223
|
"description": "Optional Jupiter Swap API base URL."
|
|
224
224
|
},
|
|
225
|
+
"jupiterSwapV2BaseUrl": {
|
|
226
|
+
"type": "string",
|
|
227
|
+
"description": "Optional Jupiter Swap API V2 base URL."
|
|
228
|
+
},
|
|
225
229
|
"jupiterUltraBaseUrl": {
|
|
226
230
|
"type": "string",
|
|
227
231
|
"description": "Optional Jupiter Ultra API base URL."
|
|
@@ -11,9 +11,12 @@ Safety rules:
|
|
|
11
11
|
- Use Kamino market/reserve reads before Kamino writes when the user needs lending context.
|
|
12
12
|
- Use Aave account reads before Aave writes when the user needs EVM lending context.
|
|
13
13
|
- For transfers, native staking, swaps, Aave writes, Jupiter Earn writes, and Kamino writes, use `preview` before `prepare` or `execute`.
|
|
14
|
+
- For Solana Jupiter swaps through `swap_solana_tokens`, prefer `intent_preview` then `intent_execute` after explicit chat confirmation. The user confirms risk limits; the backend refreshes the quote and only executes inside those limits.
|
|
15
|
+
- Solana swap intent defaults to at least 300 bps (3%) slippage, 120 seconds validity, and 3 fresh execution attempts. The backend computes the approved minimum output from the indicative output and slippage, clamps hand-tightened minimums to that floor, then executes through Jupiter Swap API V2 `/order` + `/execute` when available.
|
|
16
|
+
- Do not use legacy `execute` for Solana Jupiter swaps in OpenClaw. Exact quote-bound approval is too fragile for active markets and will be rejected by the bridge.
|
|
14
17
|
- For `swap_solana_privately`, use `preview` and then `execute` after explicit user approval. Do not use `prepare` for this tool.
|
|
15
18
|
- Use `prepare` only when the user clearly intends to produce an execution plan.
|
|
16
|
-
- Use `execute` only after the
|
|
19
|
+
- Use `execute` only after the user explicitly confirms the shown summary in chat. OpenClaw handles the internal approval token; do not ask for `/approve`, buttons, popups, or a manual token. For Solana swap intents, the token is bound to the approved intent limits instead of one fragile quote.
|
|
17
20
|
- On `mainnet`, require an approval token that includes explicit mainnet confirmation before any execution.
|
|
18
21
|
- Before any `mainnet` execute, restate the network, operation type, asset, amount, and destination, validator, or stake account.
|
|
19
22
|
- If a preview or prepare result includes `confirmation_summary` or `mainnet_warning`, surface that summary before asking for confirmation.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v0.1.26 - 2026-05-26
|
|
6
|
+
|
|
7
|
+
- Reworked Solana Jupiter swaps to prefer intent approvals, so OpenClaw confirms
|
|
8
|
+
risk limits and executes against a fresh quote instead of binding approval to
|
|
9
|
+
a fragile exact quote payload.
|
|
10
|
+
- Added Jupiter Swap API V2 `/order` + `/execute` routing with managed landing
|
|
11
|
+
support and fallback routing for Solana swaps.
|
|
12
|
+
- Hardened Solana swap intent defaults to 3% slippage, a 120-second execution
|
|
13
|
+
window, three fresh execution attempts, and safer minimum-output handling.
|
|
14
|
+
- Fixed Jupiter V2 execution payload compatibility by sending
|
|
15
|
+
`lastValidBlockHeight` in the string form expected by the API.
|
|
16
|
+
- Disabled legacy exact-preview Solana swap execute in the OpenClaw bridge to
|
|
17
|
+
prevent stale approval-token mismatches on active markets.
|
|
18
|
+
|
|
5
19
|
## v0.1.24 - 2026-05-23
|
|
6
20
|
|
|
7
21
|
- Fixed the published npm package CLI metadata so
|
package/agent-wallet/README.md
CHANGED
|
@@ -88,7 +88,7 @@ Current safe tools:
|
|
|
88
88
|
- `transfer_sol`
|
|
89
89
|
- `stake_sol_native`
|
|
90
90
|
- `transfer_spl_token`
|
|
91
|
-
- `swap_solana_tokens`
|
|
91
|
+
- `swap_solana_tokens` - Solana Jupiter swaps; prefer `intent_preview` -> chat confirmation -> `intent_execute` so execution refreshes the quote inside approved limits.
|
|
92
92
|
- `swap_solana_privately` - Houdini-backed private Solana payout flow for same-token `SOL->SOL` or `USDC->USDC` transfers to a destination wallet.
|
|
93
93
|
- `get_solana_private_swap_status`
|
|
94
94
|
- `get_jupiter_earn_tokens`
|
|
@@ -129,8 +129,9 @@ Policy defaults:
|
|
|
129
129
|
- read-only tools are always allowed
|
|
130
130
|
- `prepare` requires `user_intent=true`
|
|
131
131
|
- `prepare` does not return signed transaction bytes
|
|
132
|
-
- `execute` requires
|
|
133
|
-
-
|
|
132
|
+
- backend `execute` requires an internal authorization token bound to the exact previewed operation
|
|
133
|
+
- in OpenClaw, the extension handles that internal authorization automatically after the user explicitly confirms the shown summary in chat; do not ask OpenClaw users for `/approve`, buttons, popups, or a manual token
|
|
134
|
+
- on mainnet networks, that internal authorization must include explicit mainnet confirmation
|
|
134
135
|
- on mainnet networks, preview and prepare responses include a `confirmation_summary` and `mainnet_warning` to force a clearer final confirmation step
|
|
135
136
|
|
|
136
137
|
## Install
|
|
@@ -45,6 +45,7 @@ class Settings(BaseSettings):
|
|
|
45
45
|
wdk_evm_account_index: int = 0
|
|
46
46
|
|
|
47
47
|
jupiter_api_base_url: str = "https://lite-api.jup.ag/swap/v1"
|
|
48
|
+
jupiter_swap_v2_api_base_url: str = "https://api.jup.ag/swap/v2"
|
|
48
49
|
jupiter_ultra_api_base_url: str = "https://lite-api.jup.ag/ultra/v1"
|
|
49
50
|
jupiter_price_api_base_url: str = "https://lite-api.jup.ag/price/v3"
|
|
50
51
|
jupiter_portfolio_api_base_url: str = "https://api.jup.ag/portfolio/v1"
|
|
@@ -352,6 +352,28 @@ class OpenClawWalletAdapter:
|
|
|
352
352
|
summary[key] = value
|
|
353
353
|
return summary
|
|
354
354
|
|
|
355
|
+
if asset_type == "solana-swap-intent":
|
|
356
|
+
return {
|
|
357
|
+
"operation": action_label,
|
|
358
|
+
"network": str(payload.get("network") or getattr(self.backend, "network", "unknown")),
|
|
359
|
+
"owner": payload.get("owner"),
|
|
360
|
+
"input_mint": payload.get("input_mint"),
|
|
361
|
+
"output_mint": payload.get("output_mint"),
|
|
362
|
+
"input_amount_ui": payload.get("input_amount_ui"),
|
|
363
|
+
"input_amount_raw": payload.get("input_amount_raw"),
|
|
364
|
+
"minimum_output_amount_raw": payload.get("minimum_output_amount_raw"),
|
|
365
|
+
"minimum_output_amount_ui": payload.get("minimum_output_amount_ui"),
|
|
366
|
+
"max_slippage_bps": payload.get("max_slippage_bps"),
|
|
367
|
+
"slippage_bps": payload.get("slippage_bps"),
|
|
368
|
+
"max_fee_lamports": payload.get("max_fee_lamports"),
|
|
369
|
+
"valid_until_epoch_seconds": payload.get("valid_until_epoch_seconds"),
|
|
370
|
+
"valid_for_seconds": payload.get("valid_for_seconds"),
|
|
371
|
+
"max_attempts": payload.get("max_attempts"),
|
|
372
|
+
"allowed_providers": payload.get("allowed_providers"),
|
|
373
|
+
"recipient_policy": payload.get("recipient_policy"),
|
|
374
|
+
"spend_policy": payload.get("spend_policy"),
|
|
375
|
+
}
|
|
376
|
+
|
|
355
377
|
if asset_type == "solana-lifi-cross-chain-swap":
|
|
356
378
|
return {
|
|
357
379
|
"operation": action_label,
|
|
@@ -2451,7 +2473,9 @@ class OpenClawWalletAdapter:
|
|
|
2451
2473
|
name="swap_solana_tokens",
|
|
2452
2474
|
description=(
|
|
2453
2475
|
"Preview or execute a Solana token swap through Jupiter routing. "
|
|
2454
|
-
"
|
|
2476
|
+
"Prefer intent_preview, then intent_execute after explicit chat confirmation; "
|
|
2477
|
+
"intent_execute fetches a fresh quote and only sends if it remains inside the approved limits. "
|
|
2478
|
+
"OpenClaw should not use legacy execute for Solana swaps because exact Jupiter quote payloads expire quickly."
|
|
2455
2479
|
),
|
|
2456
2480
|
input_schema={
|
|
2457
2481
|
"type": "object",
|
|
@@ -2470,12 +2494,28 @@ class OpenClawWalletAdapter:
|
|
|
2470
2494
|
},
|
|
2471
2495
|
"slippage_bps": {
|
|
2472
2496
|
"type": "integer",
|
|
2473
|
-
"description": "Optional slippage tolerance in basis points. Defaults to
|
|
2497
|
+
"description": "Optional slippage tolerance in basis points. Defaults to 300 (3%) for Solana swaps.",
|
|
2498
|
+
},
|
|
2499
|
+
"minimum_output_amount_raw": {
|
|
2500
|
+
"type": "integer",
|
|
2501
|
+
"description": "Optional approved minimum output in raw output token units for intent_preview. For intent swaps, overly strict values are clamped to the slippage floor.",
|
|
2502
|
+
},
|
|
2503
|
+
"max_fee_lamports": {
|
|
2504
|
+
"type": "integer",
|
|
2505
|
+
"description": "Optional maximum Solana network fee in lamports for intent_preview.",
|
|
2506
|
+
},
|
|
2507
|
+
"valid_for_seconds": {
|
|
2508
|
+
"type": "integer",
|
|
2509
|
+
"description": "Optional intent validity window in seconds. Intent swaps use at least 120 seconds, max 120.",
|
|
2510
|
+
},
|
|
2511
|
+
"max_attempts": {
|
|
2512
|
+
"type": "integer",
|
|
2513
|
+
"description": "Optional number of fresh quote/simulate/execute attempts. Intent swaps use at least 3 attempts, max 5.",
|
|
2474
2514
|
},
|
|
2475
2515
|
"mode": {
|
|
2476
2516
|
"type": "string",
|
|
2477
|
-
"enum": ["preview", "prepare", "execute"],
|
|
2478
|
-
"description": "
|
|
2517
|
+
"enum": ["preview", "prepare", "execute", "intent_preview", "intent_execute"],
|
|
2518
|
+
"description": "intent_preview returns approved risk limits; intent_execute requotes and executes atomically inside those limits. Legacy preview/prepare/execute remains supported.",
|
|
2479
2519
|
},
|
|
2480
2520
|
"purpose": {
|
|
2481
2521
|
"type": "string",
|
|
@@ -4428,19 +4468,6 @@ class OpenClawWalletAdapter:
|
|
|
4428
4468
|
raise WalletBackendError(
|
|
4429
4469
|
"approved preview payload does not match the approval token. Generate a new preview and approval before execute."
|
|
4430
4470
|
)
|
|
4431
|
-
preview_summary = self._build_confirmation_summary(
|
|
4432
|
-
action_label="Flash Trade open position",
|
|
4433
|
-
payload=approved_preview,
|
|
4434
|
-
)
|
|
4435
|
-
summary_without_digest = {
|
|
4436
|
-
key: value
|
|
4437
|
-
for key, value in approval_summary_copy.items()
|
|
4438
|
-
if key != "_preview_digest"
|
|
4439
|
-
}
|
|
4440
|
-
if preview_summary != summary_without_digest:
|
|
4441
|
-
raise WalletBackendError(
|
|
4442
|
-
"approved preview payload does not match the approval token. Generate a new preview and approval before execute."
|
|
4443
|
-
)
|
|
4444
4471
|
execute_preview = dict(approved_preview)
|
|
4445
4472
|
else:
|
|
4446
4473
|
execute_preview = await active_backend.preview_flash_trade_open_position(
|
|
@@ -4567,19 +4594,6 @@ class OpenClawWalletAdapter:
|
|
|
4567
4594
|
raise WalletBackendError(
|
|
4568
4595
|
"approved preview payload does not match the approval token. Generate a new preview and approval before execute."
|
|
4569
4596
|
)
|
|
4570
|
-
preview_summary = self._build_confirmation_summary(
|
|
4571
|
-
action_label="Flash Trade close position",
|
|
4572
|
-
payload=approved_preview,
|
|
4573
|
-
)
|
|
4574
|
-
summary_without_digest = {
|
|
4575
|
-
key: value
|
|
4576
|
-
for key, value in approval_summary_copy.items()
|
|
4577
|
-
if key != "_preview_digest"
|
|
4578
|
-
}
|
|
4579
|
-
if preview_summary != summary_without_digest:
|
|
4580
|
-
raise WalletBackendError(
|
|
4581
|
-
"approved preview payload does not match the approval token. Generate a new preview and approval before execute."
|
|
4582
|
-
)
|
|
4583
4597
|
execute_preview = dict(approved_preview)
|
|
4584
4598
|
else:
|
|
4585
4599
|
execute_preview = await active_backend.preview_flash_trade_close_position(
|
|
@@ -5274,7 +5288,11 @@ class OpenClawWalletAdapter:
|
|
|
5274
5288
|
input_mint = args.get("input_mint")
|
|
5275
5289
|
output_mint = args.get("output_mint")
|
|
5276
5290
|
amount = args.get("amount")
|
|
5277
|
-
slippage_bps = args.get("slippage_bps",
|
|
5291
|
+
slippage_bps = args.get("slippage_bps", 300)
|
|
5292
|
+
minimum_output_amount_raw = args.get("minimum_output_amount_raw")
|
|
5293
|
+
max_fee_lamports = args.get("max_fee_lamports")
|
|
5294
|
+
valid_for_seconds = args.get("valid_for_seconds", 120)
|
|
5295
|
+
max_attempts = args.get("max_attempts", 3)
|
|
5278
5296
|
mode = args.get("mode")
|
|
5279
5297
|
purpose = args.get("purpose")
|
|
5280
5298
|
user_intent = args.get("user_intent", False)
|
|
@@ -5288,10 +5306,28 @@ class OpenClawWalletAdapter:
|
|
|
5288
5306
|
raise WalletBackendError("amount must be a positive number.")
|
|
5289
5307
|
if not isinstance(slippage_bps, int) or slippage_bps <= 0:
|
|
5290
5308
|
raise WalletBackendError("slippage_bps must be a positive integer.")
|
|
5291
|
-
if
|
|
5292
|
-
|
|
5309
|
+
if minimum_output_amount_raw is not None and (
|
|
5310
|
+
not isinstance(minimum_output_amount_raw, int) or minimum_output_amount_raw <= 0
|
|
5311
|
+
):
|
|
5312
|
+
raise WalletBackendError("minimum_output_amount_raw must be a positive integer when provided.")
|
|
5313
|
+
if max_fee_lamports is not None and (
|
|
5314
|
+
not isinstance(max_fee_lamports, int) or max_fee_lamports < 0
|
|
5315
|
+
):
|
|
5316
|
+
raise WalletBackendError("max_fee_lamports must be a non-negative integer when provided.")
|
|
5317
|
+
if not isinstance(valid_for_seconds, int) or valid_for_seconds <= 0 or valid_for_seconds > 120:
|
|
5318
|
+
raise WalletBackendError("valid_for_seconds must be an integer between 1 and 120.")
|
|
5319
|
+
if not isinstance(max_attempts, int) or max_attempts <= 0 or max_attempts > 5:
|
|
5320
|
+
raise WalletBackendError("max_attempts must be an integer between 1 and 5.")
|
|
5321
|
+
if mode not in {"preview", "prepare", "execute", "intent_preview", "intent_execute"}:
|
|
5322
|
+
raise WalletBackendError(
|
|
5323
|
+
"mode must be 'preview', 'prepare', 'execute', 'intent_preview' or 'intent_execute'."
|
|
5324
|
+
)
|
|
5293
5325
|
if not isinstance(purpose, str) or not purpose.strip():
|
|
5294
5326
|
raise WalletBackendError("purpose is required.")
|
|
5327
|
+
if mode in {"intent_preview", "intent_execute"}:
|
|
5328
|
+
slippage_bps = max(slippage_bps, 300)
|
|
5329
|
+
valid_for_seconds = max(valid_for_seconds, 120)
|
|
5330
|
+
max_attempts = max(max_attempts, 3)
|
|
5295
5331
|
|
|
5296
5332
|
if mode == "preview":
|
|
5297
5333
|
preview = await self.backend.preview_swap(
|
|
@@ -5310,6 +5346,27 @@ class OpenClawWalletAdapter:
|
|
|
5310
5346
|
),
|
|
5311
5347
|
)
|
|
5312
5348
|
|
|
5349
|
+
if mode == "intent_preview":
|
|
5350
|
+
intent_preview = await self.backend.preview_swap_intent(
|
|
5351
|
+
input_mint=input_mint.strip(),
|
|
5352
|
+
output_mint=output_mint.strip(),
|
|
5353
|
+
amount_ui=float(amount),
|
|
5354
|
+
slippage_bps=slippage_bps,
|
|
5355
|
+
minimum_output_amount_raw=minimum_output_amount_raw,
|
|
5356
|
+
max_fee_lamports=max_fee_lamports,
|
|
5357
|
+
valid_for_seconds=valid_for_seconds,
|
|
5358
|
+
max_attempts=max_attempts,
|
|
5359
|
+
)
|
|
5360
|
+
return AgentToolResult(
|
|
5361
|
+
tool=tool_name,
|
|
5362
|
+
ok=True,
|
|
5363
|
+
data=self._annotate_sensitive_payload(
|
|
5364
|
+
intent_preview,
|
|
5365
|
+
action_label="Swap intent",
|
|
5366
|
+
mode="preview",
|
|
5367
|
+
),
|
|
5368
|
+
)
|
|
5369
|
+
|
|
5313
5370
|
if mode == "prepare":
|
|
5314
5371
|
self._require_prepare_intent(user_intent)
|
|
5315
5372
|
preview = await self.backend.preview_swap(
|
|
@@ -5331,6 +5388,103 @@ class OpenClawWalletAdapter:
|
|
|
5331
5388
|
),
|
|
5332
5389
|
)
|
|
5333
5390
|
|
|
5391
|
+
if mode == "intent_execute":
|
|
5392
|
+
approval_payload = inspect_approval_token(
|
|
5393
|
+
approval_token,
|
|
5394
|
+
tool_name=tool_name,
|
|
5395
|
+
network=str(getattr(self.backend, "network", "unknown")),
|
|
5396
|
+
require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
|
|
5397
|
+
)
|
|
5398
|
+
approval_summary = approval_payload.get("binding", {}).get("summary")
|
|
5399
|
+
if not isinstance(approval_summary, dict):
|
|
5400
|
+
raise WalletBackendError(
|
|
5401
|
+
"approval_token does not match the requested operation. Generate a new intent preview and approval before execute."
|
|
5402
|
+
)
|
|
5403
|
+
expected_summary = {
|
|
5404
|
+
"operation": "Swap intent",
|
|
5405
|
+
"network": str(getattr(self.backend, "network", "unknown")),
|
|
5406
|
+
"input_mint": input_mint.strip(),
|
|
5407
|
+
"output_mint": output_mint.strip(),
|
|
5408
|
+
}
|
|
5409
|
+
for key, expected_value in expected_summary.items():
|
|
5410
|
+
if approval_summary.get(key) != expected_value:
|
|
5411
|
+
raise WalletBackendError(
|
|
5412
|
+
"approval_token does not match the requested swap intent. Generate a fresh intent preview and approval before execute."
|
|
5413
|
+
)
|
|
5414
|
+
current_owner = await self.backend.get_address()
|
|
5415
|
+
approved_owner = approval_summary.get("owner")
|
|
5416
|
+
if approved_owner and current_owner and str(approved_owner) != str(current_owner):
|
|
5417
|
+
raise WalletBackendError(
|
|
5418
|
+
"approval_token does not match the active wallet owner. Generate a fresh intent preview and approval before execute."
|
|
5419
|
+
)
|
|
5420
|
+
try:
|
|
5421
|
+
approved_amount = float(approval_summary.get("input_amount_ui"))
|
|
5422
|
+
approved_slippage = int(
|
|
5423
|
+
approval_summary.get("max_slippage_bps")
|
|
5424
|
+
if approval_summary.get("max_slippage_bps") is not None
|
|
5425
|
+
else approval_summary.get("slippage_bps")
|
|
5426
|
+
)
|
|
5427
|
+
except (TypeError, ValueError):
|
|
5428
|
+
raise WalletBackendError(
|
|
5429
|
+
"approval_token does not match the requested swap intent. Generate a fresh intent preview and approval before execute."
|
|
5430
|
+
)
|
|
5431
|
+
if approved_amount != float(amount) or approved_slippage != slippage_bps:
|
|
5432
|
+
raise WalletBackendError(
|
|
5433
|
+
"approval_token does not match the requested swap intent. Generate a fresh intent preview and approval before execute."
|
|
5434
|
+
)
|
|
5435
|
+
if approval_summary.get("recipient_policy") != "owner-only":
|
|
5436
|
+
raise WalletBackendError("approved swap intent recipient policy is invalid.")
|
|
5437
|
+
if approval_summary.get("spend_policy") != "exact-input":
|
|
5438
|
+
raise WalletBackendError("approved swap intent spend policy is invalid.")
|
|
5439
|
+
|
|
5440
|
+
approval_summary_copy = dict(approval_summary)
|
|
5441
|
+
self._require_execute_approval(
|
|
5442
|
+
approval_token=approval_token,
|
|
5443
|
+
tool_name=tool_name,
|
|
5444
|
+
summary=approval_summary_copy,
|
|
5445
|
+
action_label="Swap intent",
|
|
5446
|
+
)
|
|
5447
|
+
try:
|
|
5448
|
+
approved_min_output_raw = (
|
|
5449
|
+
int(approval_summary_copy["minimum_output_amount_raw"])
|
|
5450
|
+
if approval_summary_copy.get("minimum_output_amount_raw") is not None
|
|
5451
|
+
else None
|
|
5452
|
+
)
|
|
5453
|
+
approved_max_fee_lamports = (
|
|
5454
|
+
int(approval_summary_copy["max_fee_lamports"])
|
|
5455
|
+
if approval_summary_copy.get("max_fee_lamports") is not None
|
|
5456
|
+
else None
|
|
5457
|
+
)
|
|
5458
|
+
approved_valid_until = (
|
|
5459
|
+
int(approval_summary_copy["valid_until_epoch_seconds"])
|
|
5460
|
+
if approval_summary_copy.get("valid_until_epoch_seconds") is not None
|
|
5461
|
+
else None
|
|
5462
|
+
)
|
|
5463
|
+
approved_max_attempts = int(approval_summary_copy.get("max_attempts") or max_attempts)
|
|
5464
|
+
except (TypeError, ValueError):
|
|
5465
|
+
raise WalletBackendError(
|
|
5466
|
+
"approval_token does not contain valid swap intent limits. Generate a fresh intent preview and approval before execute."
|
|
5467
|
+
)
|
|
5468
|
+
result = await self.backend.execute_swap_intent(
|
|
5469
|
+
input_mint=input_mint.strip(),
|
|
5470
|
+
output_mint=output_mint.strip(),
|
|
5471
|
+
amount_ui=float(amount),
|
|
5472
|
+
slippage_bps=slippage_bps,
|
|
5473
|
+
minimum_output_amount_raw=approved_min_output_raw,
|
|
5474
|
+
max_fee_lamports=approved_max_fee_lamports,
|
|
5475
|
+
valid_until_epoch_seconds=approved_valid_until,
|
|
5476
|
+
max_attempts=approved_max_attempts,
|
|
5477
|
+
)
|
|
5478
|
+
return AgentToolResult(
|
|
5479
|
+
tool=tool_name,
|
|
5480
|
+
ok=True,
|
|
5481
|
+
data=self._annotate_sensitive_payload(
|
|
5482
|
+
result,
|
|
5483
|
+
action_label="Swap",
|
|
5484
|
+
mode="execute",
|
|
5485
|
+
),
|
|
5486
|
+
)
|
|
5487
|
+
|
|
5334
5488
|
approval_payload = inspect_approval_token(
|
|
5335
5489
|
approval_token,
|
|
5336
5490
|
tool_name=tool_name,
|
|
@@ -5376,19 +5530,6 @@ class OpenClawWalletAdapter:
|
|
|
5376
5530
|
raise WalletBackendError(
|
|
5377
5531
|
"approved preview payload does not match the approval token. Generate a new preview and approval before execute."
|
|
5378
5532
|
)
|
|
5379
|
-
preview_summary = self._build_confirmation_summary(
|
|
5380
|
-
action_label="Swap",
|
|
5381
|
-
payload=approved_preview,
|
|
5382
|
-
)
|
|
5383
|
-
summary_without_digest = {
|
|
5384
|
-
key: value
|
|
5385
|
-
for key, value in approval_summary_copy.items()
|
|
5386
|
-
if key != "_preview_digest"
|
|
5387
|
-
}
|
|
5388
|
-
if preview_summary != summary_without_digest:
|
|
5389
|
-
raise WalletBackendError(
|
|
5390
|
-
"approved preview payload does not match the approval token. Generate a new preview and approval before execute."
|
|
5391
|
-
)
|
|
5392
5533
|
execute_preview = dict(approved_preview)
|
|
5393
5534
|
else:
|
|
5394
5535
|
execute_preview = await self.backend.preview_swap(
|
|
@@ -5525,19 +5666,6 @@ class OpenClawWalletAdapter:
|
|
|
5525
5666
|
raise WalletBackendError(
|
|
5526
5667
|
"approved preview payload does not match the approval token. Generate a new preview and approval before execute."
|
|
5527
5668
|
)
|
|
5528
|
-
preview_summary = self._build_confirmation_summary(
|
|
5529
|
-
action_label="Solana private swap",
|
|
5530
|
-
payload=approved_preview,
|
|
5531
|
-
)
|
|
5532
|
-
summary_without_digest = {
|
|
5533
|
-
key: value
|
|
5534
|
-
for key, value in approval_summary_copy.items()
|
|
5535
|
-
if key != "_preview_digest"
|
|
5536
|
-
}
|
|
5537
|
-
if preview_summary != summary_without_digest:
|
|
5538
|
-
raise WalletBackendError(
|
|
5539
|
-
"approved preview payload does not match the approval token. Generate a new preview and approval before execute."
|
|
5540
|
-
)
|
|
5541
5669
|
execute_preview = dict(approved_preview)
|
|
5542
5670
|
|
|
5543
5671
|
self._require_execute_approval(
|
|
@@ -5593,20 +5721,6 @@ class OpenClawWalletAdapter:
|
|
|
5593
5721
|
raise WalletBackendError(
|
|
5594
5722
|
"approved preview payload does not match the approval token. Generate a new preview and approval before continue."
|
|
5595
5723
|
)
|
|
5596
|
-
preview_summary = self._build_confirmation_summary(
|
|
5597
|
-
action_label="Solana private swap",
|
|
5598
|
-
payload=approved_preview,
|
|
5599
|
-
)
|
|
5600
|
-
summary_without_digest = {
|
|
5601
|
-
key: value
|
|
5602
|
-
for key, value in approval_summary_copy.items()
|
|
5603
|
-
if key != "_preview_digest"
|
|
5604
|
-
}
|
|
5605
|
-
if preview_summary != summary_without_digest:
|
|
5606
|
-
raise WalletBackendError(
|
|
5607
|
-
"approved preview payload does not match the approval token. Generate a new preview and approval before continue."
|
|
5608
|
-
)
|
|
5609
|
-
|
|
5610
5724
|
self._require_execute_approval(
|
|
5611
5725
|
approval_token=approval_token,
|
|
5612
5726
|
tool_name="swap_solana_privately",
|
|
@@ -93,6 +93,7 @@ def _apply_config_overrides(config: dict[str, Any]) -> None:
|
|
|
93
93
|
),
|
|
94
94
|
"openclawHome": ("OPENCLAW_HOME", config.get("openclawHome"), True),
|
|
95
95
|
"jupiterBaseUrl": ("JUPITER_API_BASE_URL", config.get("jupiterBaseUrl"), True),
|
|
96
|
+
"jupiterSwapV2BaseUrl": ("JUPITER_SWAP_V2_API_BASE_URL", config.get("jupiterSwapV2BaseUrl"), True),
|
|
96
97
|
"jupiterUltraBaseUrl": ("JUPITER_ULTRA_API_BASE_URL", config.get("jupiterUltraBaseUrl"), True),
|
|
97
98
|
"jupiterPriceBaseUrl": ("JUPITER_PRICE_API_BASE_URL", config.get("jupiterPriceBaseUrl"), True),
|
|
98
99
|
"jupiterPortfolioBaseUrl": (
|
|
@@ -91,6 +91,13 @@ def _direct_jupiter_enabled() -> bool:
|
|
|
91
91
|
return bool(settings.jupiter_api_key.strip())
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
def _swap_v2_base_url() -> str:
|
|
95
|
+
return os.getenv(
|
|
96
|
+
"JUPITER_SWAP_V2_API_BASE_URL",
|
|
97
|
+
settings.jupiter_swap_v2_api_base_url,
|
|
98
|
+
).strip().rstrip("/")
|
|
99
|
+
|
|
100
|
+
|
|
94
101
|
def _unwrap_gateway_payload(
|
|
95
102
|
status_code: int,
|
|
96
103
|
payload: Any,
|
|
@@ -367,6 +374,52 @@ async def fetch_ultra_order(
|
|
|
367
374
|
return data
|
|
368
375
|
|
|
369
376
|
|
|
377
|
+
async def fetch_swap_v2_order(
|
|
378
|
+
*,
|
|
379
|
+
input_mint: str,
|
|
380
|
+
output_mint: str,
|
|
381
|
+
amount_raw: int,
|
|
382
|
+
taker: str,
|
|
383
|
+
slippage_bps: int | str | None = None,
|
|
384
|
+
exclude_routers: list[str] | None = None,
|
|
385
|
+
swap_mode: str = "ExactIn",
|
|
386
|
+
) -> dict[str, Any]:
|
|
387
|
+
"""Fetch a Jupiter Swap API V2 meta-aggregator order."""
|
|
388
|
+
client = get_client()
|
|
389
|
+
params: dict[str, Any] = {
|
|
390
|
+
"inputMint": input_mint,
|
|
391
|
+
"outputMint": output_mint,
|
|
392
|
+
"amount": str(amount_raw),
|
|
393
|
+
"taker": taker,
|
|
394
|
+
}
|
|
395
|
+
if swap_mode != "ExactIn":
|
|
396
|
+
params["swapMode"] = swap_mode
|
|
397
|
+
if slippage_bps is not None:
|
|
398
|
+
params["slippageBps"] = str(slippage_bps)
|
|
399
|
+
if exclude_routers:
|
|
400
|
+
params["excludeRouters"] = ",".join(str(item).strip() for item in exclude_routers if str(item).strip())
|
|
401
|
+
|
|
402
|
+
response = await client.get(
|
|
403
|
+
f"{_swap_v2_base_url()}/order",
|
|
404
|
+
params=params,
|
|
405
|
+
headers=_headers(),
|
|
406
|
+
)
|
|
407
|
+
if response.status_code != 200:
|
|
408
|
+
raise ProviderError("jupiter-v2", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
409
|
+
data = response.json()
|
|
410
|
+
if not isinstance(data, dict):
|
|
411
|
+
raise ProviderError("jupiter-v2", "Unexpected order response from Jupiter Swap V2.")
|
|
412
|
+
if data.get("error") or data.get("errorCode"):
|
|
413
|
+
raise ProviderError(
|
|
414
|
+
"jupiter-v2",
|
|
415
|
+
str(data.get("error") or data.get("errorCode") or "Unknown Swap V2 order error."),
|
|
416
|
+
details=data,
|
|
417
|
+
)
|
|
418
|
+
if "outAmount" not in data:
|
|
419
|
+
raise ProviderError("jupiter-v2", "Unexpected order response from Jupiter Swap V2.")
|
|
420
|
+
return data
|
|
421
|
+
|
|
422
|
+
|
|
370
423
|
async def build_swap_transaction(
|
|
371
424
|
*,
|
|
372
425
|
user_public_key: str,
|
|
@@ -477,6 +530,42 @@ async def execute_ultra_order(
|
|
|
477
530
|
return data
|
|
478
531
|
|
|
479
532
|
|
|
533
|
+
async def execute_swap_v2_order(
|
|
534
|
+
*,
|
|
535
|
+
signed_transaction_base64: str,
|
|
536
|
+
request_id: str,
|
|
537
|
+
last_valid_block_height: int | str | None = None,
|
|
538
|
+
) -> dict[str, Any]:
|
|
539
|
+
"""Execute a signed Jupiter Swap API V2 order."""
|
|
540
|
+
client = get_client()
|
|
541
|
+
body: dict[str, Any] = {
|
|
542
|
+
"signedTransaction": signed_transaction_base64,
|
|
543
|
+
"requestId": request_id,
|
|
544
|
+
}
|
|
545
|
+
if last_valid_block_height is not None:
|
|
546
|
+
body["lastValidBlockHeight"] = str(last_valid_block_height)
|
|
547
|
+
response = await client.post(
|
|
548
|
+
f"{_swap_v2_base_url()}/execute",
|
|
549
|
+
json=body,
|
|
550
|
+
headers={**_headers(), "Content-Type": "application/json"},
|
|
551
|
+
)
|
|
552
|
+
if response.status_code != 200:
|
|
553
|
+
raise ProviderError("jupiter-v2", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
554
|
+
data = response.json()
|
|
555
|
+
if not isinstance(data, dict):
|
|
556
|
+
raise ProviderError("jupiter-v2", "Unexpected execute response from Jupiter Swap V2.")
|
|
557
|
+
if data.get("error") or data.get("errorCode"):
|
|
558
|
+
raise ProviderError(
|
|
559
|
+
"jupiter-v2",
|
|
560
|
+
str(data.get("error") or data.get("errorCode") or "Unknown Swap V2 execute error."),
|
|
561
|
+
details=data,
|
|
562
|
+
)
|
|
563
|
+
if str(data.get("status") or "").strip().lower() == "failed":
|
|
564
|
+
message = data.get("error") or data.get("code") or "Swap V2 execute failed."
|
|
565
|
+
raise ProviderError("jupiter-v2", str(message), details=data)
|
|
566
|
+
return data
|
|
567
|
+
|
|
568
|
+
|
|
480
569
|
async def fetch_prices(
|
|
481
570
|
*,
|
|
482
571
|
mints: list[str],
|