@agentlayer.tech/wallet 0.1.26 → 0.1.27

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.
@@ -1535,6 +1535,7 @@ const solanaToolDefinitions = [
1535
1535
  market: { type: "string" },
1536
1536
  reserve: { type: "string" },
1537
1537
  amount_ui: { type: "string" },
1538
+ obligation_address: { type: "string" },
1538
1539
  mode: { type: "string", enum: ["preview", "prepare", "execute"] },
1539
1540
  purpose: { type: "string" },
1540
1541
  user_intent: { type: "boolean" },
@@ -1553,6 +1554,7 @@ const solanaToolDefinitions = [
1553
1554
  market: { type: "string" },
1554
1555
  reserve: { type: "string" },
1555
1556
  amount_ui: { type: "string" },
1557
+ obligation_address: { type: "string" },
1556
1558
  mode: { type: "string", enum: ["preview", "prepare", "execute"] },
1557
1559
  purpose: { type: "string" },
1558
1560
  user_intent: { type: "boolean" },
@@ -1571,6 +1573,7 @@ const solanaToolDefinitions = [
1571
1573
  market: { type: "string" },
1572
1574
  reserve: { type: "string" },
1573
1575
  amount_ui: { type: "string" },
1576
+ obligation_address: { type: "string" },
1574
1577
  mode: { type: "string", enum: ["preview", "prepare", "execute"] },
1575
1578
  purpose: { type: "string" },
1576
1579
  user_intent: { type: "boolean" },
@@ -1535,6 +1535,7 @@ const solanaToolDefinitions = [
1535
1535
  market: { type: "string" },
1536
1536
  reserve: { type: "string" },
1537
1537
  amount_ui: { type: "string" },
1538
+ obligation_address: { type: "string" },
1538
1539
  mode: { type: "string", enum: ["preview", "prepare", "execute"] },
1539
1540
  purpose: { type: "string" },
1540
1541
  user_intent: { type: "boolean" },
@@ -1553,6 +1554,7 @@ const solanaToolDefinitions = [
1553
1554
  market: { type: "string" },
1554
1555
  reserve: { type: "string" },
1555
1556
  amount_ui: { type: "string" },
1557
+ obligation_address: { type: "string" },
1556
1558
  mode: { type: "string", enum: ["preview", "prepare", "execute"] },
1557
1559
  purpose: { type: "string" },
1558
1560
  user_intent: { type: "boolean" },
@@ -1571,6 +1573,7 @@ const solanaToolDefinitions = [
1571
1573
  market: { type: "string" },
1572
1574
  reserve: { type: "string" },
1573
1575
  amount_ui: { type: "string" },
1576
+ obligation_address: { type: "string" },
1574
1577
  mode: { type: "string", enum: ["preview", "prepare", "execute"] },
1575
1578
  purpose: { type: "string" },
1576
1579
  user_intent: { type: "boolean" },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayertech/agent-wallet-plugin",
3
- "version": "0.1.24",
3
+ "version": "0.1.27",
4
4
  "description": "OpenClaw plugin bridge for the AgentLayer wallet runtime.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN ../../../LICENSE",
@@ -13,6 +13,7 @@ Safety rules:
13
13
  - For transfers, native staking, swaps, Aave writes, Jupiter Earn writes, and Kamino writes, use `preview` before `prepare` or `execute`.
14
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
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
+ - Metis `/swap` fallback builds use Jupiter dynamic slippage and a bounded `veryHigh` priority fee instead of the old `"auto"` priority mode.
16
17
  - 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.
17
18
  - For `swap_solana_privately`, use `preview` and then `execute` after explicit user approval. Do not use `prepare` for this tool.
18
19
  - Use `prepare` only when the user clearly intends to produce an execution plan.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## v0.1.27 - 2026-05-27
6
+
7
+ - Improved Solana swap fallback landing by enabling Jupiter dynamic slippage
8
+ and bounded `veryHigh` priority fees on the Metis `/swap` fallback path.
9
+ - Hardened Kamino transaction execution with local simulation before send,
10
+ Kamino-specific build timeouts, and longer confirmation polling on mainnet.
11
+ - Reused approved Kamino preview payloads during execute so OpenClaw no longer
12
+ needs to rebuild the same write path just to satisfy approval binding.
13
+ - Added Kamino obligation pinning for `withdraw`, `borrow`, and `repay`, so
14
+ preview can require an explicit `obligation_address` and execute verifies the
15
+ built transaction references the selected obligation before signing.
16
+
5
17
  ## v0.1.26 - 2026-05-26
6
18
 
7
19
  - Reworked Solana Jupiter swaps to prefer intent approvals, so OpenClaw confirms
@@ -760,6 +760,7 @@ class OpenClawWalletAdapter:
760
760
  "owner",
761
761
  "authority",
762
762
  "address",
763
+ "obligation_address",
763
764
  "market",
764
765
  "reserve",
765
766
  "amount_native",
@@ -2969,6 +2970,10 @@ class OpenClawWalletAdapter:
2969
2970
  "type": "string",
2970
2971
  "description": "Decimal token amount to withdraw, as a string.",
2971
2972
  },
2973
+ "obligation_address": {
2974
+ "type": "string",
2975
+ "description": "Optional Kamino obligation address. Required when preview shows multiple matching obligations.",
2976
+ },
2972
2977
  "mode": {
2973
2978
  "type": "string",
2974
2979
  "enum": ["preview", "prepare", "execute"],
@@ -3003,6 +3008,10 @@ class OpenClawWalletAdapter:
3003
3008
  "type": "string",
3004
3009
  "description": "Decimal token amount to borrow, as a string.",
3005
3010
  },
3011
+ "obligation_address": {
3012
+ "type": "string",
3013
+ "description": "Optional Kamino obligation address. Required when preview shows multiple obligations in the selected market.",
3014
+ },
3006
3015
  "mode": {
3007
3016
  "type": "string",
3008
3017
  "enum": ["preview", "prepare", "execute"],
@@ -3037,6 +3046,10 @@ class OpenClawWalletAdapter:
3037
3046
  "type": "string",
3038
3047
  "description": "Decimal token amount to repay, as a string.",
3039
3048
  },
3049
+ "obligation_address": {
3050
+ "type": "string",
3051
+ "description": "Optional Kamino obligation address. Required when preview shows multiple matching debt obligations.",
3052
+ },
3040
3053
  "mode": {
3041
3054
  "type": "string",
3042
3055
  "enum": ["preview", "prepare", "execute"],
@@ -5003,6 +5016,7 @@ class OpenClawWalletAdapter:
5003
5016
  market = args.get("market")
5004
5017
  reserve = args.get("reserve")
5005
5018
  amount_ui = args.get("amount_ui")
5019
+ obligation_address = args.get("obligation_address")
5006
5020
  mode = args.get("mode")
5007
5021
  purpose = args.get("purpose")
5008
5022
  user_intent = args.get("user_intent", False)
@@ -5014,6 +5028,8 @@ class OpenClawWalletAdapter:
5014
5028
  raise WalletBackendError("reserve is required.")
5015
5029
  if not isinstance(amount_ui, str) or not amount_ui.strip():
5016
5030
  raise WalletBackendError("amount_ui is required.")
5031
+ if obligation_address is not None and not isinstance(obligation_address, str):
5032
+ raise WalletBackendError("obligation_address must be a string when provided.")
5017
5033
  if mode not in {"preview", "prepare", "execute"}:
5018
5034
  raise WalletBackendError("mode must be 'preview', 'prepare' or 'execute'.")
5019
5035
  if not isinstance(purpose, str) or not purpose.strip():
@@ -5046,6 +5062,7 @@ class OpenClawWalletAdapter:
5046
5062
  market=market.strip(),
5047
5063
  reserve=reserve.strip(),
5048
5064
  amount_ui=amount_ui.strip(),
5065
+ obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
5049
5066
  )
5050
5067
  return AgentToolResult(
5051
5068
  tool=tool_name,
@@ -5063,7 +5080,12 @@ class OpenClawWalletAdapter:
5063
5080
  market=market.strip(),
5064
5081
  reserve=reserve.strip(),
5065
5082
  amount_ui=amount_ui.strip(),
5083
+ obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
5066
5084
  )
5085
+ if bool(preview.get("requires_obligation_address")):
5086
+ raise WalletBackendError(
5087
+ f"{action_label} requires obligation_address when multiple Kamino obligations match the selected position."
5088
+ )
5067
5089
  return AgentToolResult(
5068
5090
  tool=tool_name,
5069
5091
  ok=True,
@@ -5077,24 +5099,49 @@ class OpenClawWalletAdapter:
5077
5099
  ),
5078
5100
  )
5079
5101
 
5080
- execute_preview = await preview_method(
5081
- market=market.strip(),
5082
- reserve=reserve.strip(),
5083
- amount_ui=amount_ui.strip(),
5102
+ approved_preview = args.get("_approved_preview")
5103
+ execute_preview = None
5104
+ approval_payload = inspect_approval_token(
5105
+ approval_token,
5106
+ tool_name=tool_name,
5107
+ network=str(getattr(self.backend, "network", "unknown")),
5108
+ require_mainnet_confirmation=self._is_mainnet_for_backend(self.backend),
5084
5109
  )
5110
+ approval_summary = approval_payload.get("binding", {}).get("summary")
5111
+ if not isinstance(approval_summary, dict):
5112
+ raise WalletBackendError(
5113
+ "approval_token does not match the requested operation. Generate a new approval after previewing the exact action."
5114
+ )
5115
+ approval_summary_copy = dict(approval_summary)
5116
+ if isinstance(approval_summary_copy.get("_preview_digest"), str):
5117
+ if not isinstance(approved_preview, dict):
5118
+ raise WalletBackendError(
5119
+ f"Approved {action_label} preview payload is required for execute mode. Generate a new preview and approval before execute."
5120
+ )
5121
+ if preview_payload_digest(approved_preview) != approval_summary_copy["_preview_digest"]:
5122
+ raise WalletBackendError(
5123
+ "approved preview payload does not match the approval token. Generate a new preview and approval before execute."
5124
+ )
5125
+ execute_preview = dict(approved_preview)
5126
+ else:
5127
+ execute_preview = await preview_method(
5128
+ market=market.strip(),
5129
+ reserve=reserve.strip(),
5130
+ amount_ui=amount_ui.strip(),
5131
+ obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
5132
+ )
5085
5133
  self._require_execute_approval(
5086
5134
  approval_token=approval_token,
5087
5135
  tool_name=tool_name,
5088
- summary=self._build_confirmation_summary(
5089
- action_label=action_label,
5090
- payload=execute_preview,
5091
- ),
5136
+ summary=approval_summary_copy,
5092
5137
  action_label=action_label,
5093
5138
  )
5094
5139
  result = await execute_method(
5095
5140
  market=market.strip(),
5096
5141
  reserve=reserve.strip(),
5097
5142
  amount_ui=amount_ui.strip(),
5143
+ obligation_address=obligation_address.strip() if isinstance(obligation_address, str) and obligation_address.strip() else None,
5144
+ approved_preview=execute_preview,
5098
5145
  )
5099
5146
  return AgentToolResult(
5100
5147
  tool=tool_name,
@@ -10,6 +10,8 @@ from agent_wallet.config import settings
10
10
  from agent_wallet.exceptions import ProviderError
11
11
  from agent_wallet.http_client import get_client
12
12
 
13
+ JUPITER_SWAP_FALLBACK_PRIORITY_MAX_LAMPORTS = 2_000_000
14
+
13
15
 
14
16
  def _headers() -> dict[str, str]:
15
17
  headers = {"Accept": "application/json"}
@@ -91,6 +93,32 @@ def _direct_jupiter_enabled() -> bool:
91
93
  return bool(settings.jupiter_api_key.strip())
92
94
 
93
95
 
96
+ def _swap_fallback_prioritization_fee() -> dict[str, Any]:
97
+ return {
98
+ "priorityLevelWithMaxLamports": {
99
+ "priorityLevel": "veryHigh",
100
+ "maxLamports": JUPITER_SWAP_FALLBACK_PRIORITY_MAX_LAMPORTS,
101
+ "global": False,
102
+ }
103
+ }
104
+
105
+
106
+ def _swap_fallback_build_body(
107
+ *,
108
+ user_public_key: str,
109
+ quote_response: dict[str, Any],
110
+ wrap_and_unwrap_sol: bool,
111
+ ) -> dict[str, Any]:
112
+ return {
113
+ "userPublicKey": user_public_key,
114
+ "quoteResponse": quote_response,
115
+ "wrapAndUnwrapSol": wrap_and_unwrap_sol,
116
+ "dynamicComputeUnitLimit": True,
117
+ "dynamicSlippage": True,
118
+ "prioritizationFeeLamports": _swap_fallback_prioritization_fee(),
119
+ }
120
+
121
+
94
122
  def _swap_v2_base_url() -> str:
95
123
  return os.getenv(
96
124
  "JUPITER_SWAP_V2_API_BASE_URL",
@@ -456,13 +484,11 @@ async def _build_swap_direct(
456
484
  ) -> dict[str, Any]:
457
485
  """Build a swap transaction directly via Jupiter API."""
458
486
  client = get_client()
459
- body = {
460
- "userPublicKey": user_public_key,
461
- "quoteResponse": quote_response,
462
- "wrapAndUnwrapSol": wrap_and_unwrap_sol,
463
- "dynamicComputeUnitLimit": True,
464
- "prioritizationFeeLamports": "auto",
465
- }
487
+ body = _swap_fallback_build_body(
488
+ user_public_key=user_public_key,
489
+ quote_response=quote_response,
490
+ wrap_and_unwrap_sol=wrap_and_unwrap_sol,
491
+ )
466
492
  response = await client.post(
467
493
  f"{settings.jupiter_api_base_url.rstrip('/')}/swap",
468
494
  json=body,
@@ -483,13 +509,11 @@ async def _build_swap_via_gateway(
483
509
  wrap_and_unwrap_sol: bool = True,
484
510
  ) -> dict[str, Any]:
485
511
  """Build a swap transaction via provider gateway (uses API key)."""
486
- body = {
487
- "userPublicKey": user_public_key,
488
- "quoteResponse": quote_response,
489
- "wrapAndUnwrapSol": wrap_and_unwrap_sol,
490
- "dynamicComputeUnitLimit": True,
491
- "prioritizationFeeLamports": "auto",
492
- }
512
+ body = _swap_fallback_build_body(
513
+ user_public_key=user_public_key,
514
+ quote_response=quote_response,
515
+ wrap_and_unwrap_sol=wrap_and_unwrap_sol,
516
+ )
493
517
  status_code, payload = await _gateway_post("swap", body=body)
494
518
  if status_code != 200:
495
519
  error_msg = payload if isinstance(payload, str) else json.dumps(payload)
@@ -8,6 +8,8 @@ from agent_wallet.config import settings
8
8
  from agent_wallet.exceptions import ProviderError
9
9
  from agent_wallet.http_client import get_client
10
10
 
11
+ KAMINO_BUILD_TIMEOUT_SECONDS = 20.0
12
+
11
13
 
12
14
  def _normalized_api_base() -> str:
13
15
  return settings.kamino_api_base_url.rstrip("/")
@@ -140,6 +142,7 @@ async def build_lend_deposit_transaction(
140
142
  "reserve": reserve,
141
143
  "amount": amount_ui,
142
144
  },
145
+ timeout=KAMINO_BUILD_TIMEOUT_SECONDS,
143
146
  )
144
147
  if response.status_code != 200:
145
148
  raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
@@ -163,6 +166,7 @@ async def build_lend_withdraw_transaction(
163
166
  "reserve": reserve,
164
167
  "amount": amount_ui,
165
168
  },
169
+ timeout=KAMINO_BUILD_TIMEOUT_SECONDS,
166
170
  )
167
171
  if response.status_code != 200:
168
172
  raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
@@ -186,6 +190,7 @@ async def build_lend_borrow_transaction(
186
190
  "reserve": reserve,
187
191
  "amount": amount_ui,
188
192
  },
193
+ timeout=KAMINO_BUILD_TIMEOUT_SECONDS,
189
194
  )
190
195
  if response.status_code != 200:
191
196
  raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
@@ -209,6 +214,7 @@ async def build_lend_repay_transaction(
209
214
  "reserve": reserve,
210
215
  "amount": amount_ui,
211
216
  },
217
+ timeout=KAMINO_BUILD_TIMEOUT_SECONDS,
212
218
  )
213
219
  if response.status_code != 200:
214
220
  raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
@@ -718,6 +718,7 @@ def verify_provider_kamino_lend_transaction(
718
718
  market_address: str,
719
719
  reserve_address: str,
720
720
  action: str,
721
+ obligation_address: str | None = None,
721
722
  loaded_addresses: list[str] | None = None,
722
723
  ) -> dict[str, Any]:
723
724
  binding = _assert_basic_wallet_binding(
@@ -734,6 +735,10 @@ def verify_provider_kamino_lend_transaction(
734
735
  raise WalletBackendError(
735
736
  f"{action} transaction does not reference the expected Kamino reserve."
736
737
  )
738
+ if obligation_address and obligation_address not in keys:
739
+ raise WalletBackendError(
740
+ f"{action} transaction does not reference the expected Kamino obligation."
741
+ )
737
742
  program_ids = _program_ids(message, loaded_addresses)
738
743
  unknown_program_ids = _assert_program_allowlist(
739
744
  program_ids,
@@ -764,6 +769,7 @@ def verify_provider_kamino_lend_transaction(
764
769
  "instruction_count": len(_compiled_instructions(message)),
765
770
  "market_address": market_address,
766
771
  "reserve_address": reserve_address,
772
+ "obligation_address": obligation_address,
767
773
  "action": action,
768
774
  "verified": True,
769
775
  }
@@ -525,6 +525,7 @@ class AgentWalletBackend(ABC):
525
525
  market: str,
526
526
  reserve: str,
527
527
  amount_ui: str,
528
+ obligation_address: str | None = None,
528
529
  ) -> dict[str, Any]:
529
530
  raise WalletBackendError(f"{self.name} does not support Kamino deposit previews.")
530
531
 
@@ -533,6 +534,8 @@ class AgentWalletBackend(ABC):
533
534
  market: str,
534
535
  reserve: str,
535
536
  amount_ui: str,
537
+ obligation_address: str | None = None,
538
+ approved_preview: dict[str, Any] | None = None,
536
539
  ) -> dict[str, Any]:
537
540
  raise WalletBackendError(f"{self.name} does not support Kamino deposit preparation.")
538
541
 
@@ -541,6 +544,8 @@ class AgentWalletBackend(ABC):
541
544
  market: str,
542
545
  reserve: str,
543
546
  amount_ui: str,
547
+ obligation_address: str | None = None,
548
+ approved_preview: dict[str, Any] | None = None,
544
549
  ) -> dict[str, Any]:
545
550
  raise WalletBackendError(f"{self.name} does not support Kamino deposits.")
546
551
 
@@ -549,6 +554,7 @@ class AgentWalletBackend(ABC):
549
554
  market: str,
550
555
  reserve: str,
551
556
  amount_ui: str,
557
+ obligation_address: str | None = None,
552
558
  ) -> dict[str, Any]:
553
559
  raise WalletBackendError(f"{self.name} does not support Kamino withdraw previews.")
554
560
 
@@ -557,6 +563,8 @@ class AgentWalletBackend(ABC):
557
563
  market: str,
558
564
  reserve: str,
559
565
  amount_ui: str,
566
+ obligation_address: str | None = None,
567
+ approved_preview: dict[str, Any] | None = None,
560
568
  ) -> dict[str, Any]:
561
569
  raise WalletBackendError(f"{self.name} does not support Kamino withdraw preparation.")
562
570
 
@@ -565,6 +573,8 @@ class AgentWalletBackend(ABC):
565
573
  market: str,
566
574
  reserve: str,
567
575
  amount_ui: str,
576
+ obligation_address: str | None = None,
577
+ approved_preview: dict[str, Any] | None = None,
568
578
  ) -> dict[str, Any]:
569
579
  raise WalletBackendError(f"{self.name} does not support Kamino withdraws.")
570
580
 
@@ -573,6 +583,7 @@ class AgentWalletBackend(ABC):
573
583
  market: str,
574
584
  reserve: str,
575
585
  amount_ui: str,
586
+ obligation_address: str | None = None,
576
587
  ) -> dict[str, Any]:
577
588
  raise WalletBackendError(f"{self.name} does not support Kamino borrow previews.")
578
589
 
@@ -581,6 +592,8 @@ class AgentWalletBackend(ABC):
581
592
  market: str,
582
593
  reserve: str,
583
594
  amount_ui: str,
595
+ obligation_address: str | None = None,
596
+ approved_preview: dict[str, Any] | None = None,
584
597
  ) -> dict[str, Any]:
585
598
  raise WalletBackendError(f"{self.name} does not support Kamino borrow preparation.")
586
599
 
@@ -589,6 +602,8 @@ class AgentWalletBackend(ABC):
589
602
  market: str,
590
603
  reserve: str,
591
604
  amount_ui: str,
605
+ obligation_address: str | None = None,
606
+ approved_preview: dict[str, Any] | None = None,
592
607
  ) -> dict[str, Any]:
593
608
  raise WalletBackendError(f"{self.name} does not support Kamino borrows.")
594
609
 
@@ -597,6 +612,7 @@ class AgentWalletBackend(ABC):
597
612
  market: str,
598
613
  reserve: str,
599
614
  amount_ui: str,
615
+ obligation_address: str | None = None,
600
616
  ) -> dict[str, Any]:
601
617
  raise WalletBackendError(f"{self.name} does not support Kamino repay previews.")
602
618
 
@@ -605,6 +621,8 @@ class AgentWalletBackend(ABC):
605
621
  market: str,
606
622
  reserve: str,
607
623
  amount_ui: str,
624
+ obligation_address: str | None = None,
625
+ approved_preview: dict[str, Any] | None = None,
608
626
  ) -> dict[str, Any]:
609
627
  raise WalletBackendError(f"{self.name} does not support Kamino repay preparation.")
610
628
 
@@ -613,6 +631,8 @@ class AgentWalletBackend(ABC):
613
631
  market: str,
614
632
  reserve: str,
615
633
  amount_ui: str,
634
+ obligation_address: str | None = None,
635
+ approved_preview: dict[str, Any] | None = None,
616
636
  ) -> dict[str, Any]:
617
637
  raise WalletBackendError(f"{self.name} does not support Kamino repays.")
618
638
 
@@ -3765,19 +3765,7 @@ class SolanaWalletBackend(AgentWalletBackend):
3765
3765
  keypair = Keypair.from_bytes(self.signer.export_keypair_bytes())
3766
3766
  try:
3767
3767
  unsigned_transaction = VersionedTransaction.from_bytes(raw_transaction)
3768
- signature = keypair.sign_message(to_bytes_versioned(unsigned_transaction.message))
3769
- signatures = list(unsigned_transaction.signatures)
3770
- if wallet_signer_index >= len(signatures):
3771
- raise WalletBackendError(
3772
- "Provider transaction signer layout is incompatible with local signing."
3773
- )
3774
- signatures[wallet_signer_index] = signature
3775
- signed_transaction = VersionedTransaction.populate(
3776
- unsigned_transaction.message,
3777
- signatures,
3778
- )
3779
- return encode_transaction_base64(bytes(signed_transaction))
3780
- except Exception:
3768
+ except (TypeError, ValueError):
3781
3769
  unsigned_transaction = Transaction.from_bytes(raw_transaction)
3782
3770
  signatures = list(unsigned_transaction.signatures)
3783
3771
  if wallet_signer_index >= len(signatures):
@@ -3789,6 +3777,18 @@ class SolanaWalletBackend(AgentWalletBackend):
3789
3777
  unsigned_transaction.message.recent_blockhash,
3790
3778
  )
3791
3779
  return encode_transaction_base64(bytes(unsigned_transaction))
3780
+ signature = keypair.sign_message(to_bytes_versioned(unsigned_transaction.message))
3781
+ signatures = list(unsigned_transaction.signatures)
3782
+ if wallet_signer_index >= len(signatures):
3783
+ raise WalletBackendError(
3784
+ "Provider transaction signer layout is incompatible with local signing."
3785
+ )
3786
+ signatures[wallet_signer_index] = signature
3787
+ signed_transaction = VersionedTransaction.populate(
3788
+ unsigned_transaction.message,
3789
+ signatures,
3790
+ )
3791
+ return encode_transaction_base64(bytes(signed_transaction))
3792
3792
 
3793
3793
  async def _prepare_jupiter_lend_transaction(
3794
3794
  self,
@@ -3847,9 +3847,11 @@ class SolanaWalletBackend(AgentWalletBackend):
3847
3847
  raise WalletBackendError(
3848
3848
  "This wallet backend is in sign-only mode. Disable sign_only to broadcast transactions."
3849
3849
  )
3850
+ kamino_verified = bool((prepared.get("kamino_safety") or {}).get("verified"))
3850
3851
  submitted = await solana_rpc.send_transaction(
3851
3852
  transaction_base64=str(prepared["transaction_base64"]),
3852
3853
  rpc_url=self.rpc_urls,
3854
+ skip_preflight=source == "kamino" and kamino_verified,
3853
3855
  )
3854
3856
  signature = submitted.get("signature")
3855
3857
  status = None
@@ -3858,6 +3860,8 @@ class SolanaWalletBackend(AgentWalletBackend):
3858
3860
  status = await solana_rpc.wait_for_confirmation(
3859
3861
  signature=signature,
3860
3862
  rpc_url=self.rpc_urls,
3863
+ timeout_seconds=60.0 if source == "kamino" else 20.0,
3864
+ poll_interval_seconds=2.0 if source == "kamino" else 1.0,
3861
3865
  )
3862
3866
  confirmed = status is not None
3863
3867
  return {
@@ -3875,6 +3879,8 @@ class SolanaWalletBackend(AgentWalletBackend):
3875
3879
  "slot": status.get("slot") if status else None,
3876
3880
  "sign_only": self.sign_only,
3877
3881
  "source": source,
3882
+ "simulation": prepared.get("simulation"),
3883
+ "kamino_safety": prepared.get("kamino_safety"),
3878
3884
  }
3879
3885
 
3880
3886
  async def _execute_prepared_jupiter_lend_transaction(self, prepared: dict[str, Any]) -> dict[str, Any]:
@@ -3925,6 +3931,37 @@ class SolanaWalletBackend(AgentWalletBackend):
3925
3931
  matches.append(item)
3926
3932
  return matches
3927
3933
 
3934
+ def _resolve_kamino_obligation_selection(
3935
+ self,
3936
+ *,
3937
+ obligations: list[Any],
3938
+ obligation_address: str | None,
3939
+ action: str,
3940
+ ) -> tuple[list[dict[str, Any]], dict[str, Any] | None]:
3941
+ candidates = [item for item in obligations if isinstance(item, dict)]
3942
+ requested = str(obligation_address or "").strip()
3943
+ if requested:
3944
+ requested = validate_solana_address(requested)
3945
+ for item in candidates:
3946
+ if (
3947
+ _kamino_entry_address(
3948
+ item,
3949
+ "obligationAddress",
3950
+ "obligation",
3951
+ "address",
3952
+ "pubkey",
3953
+ "loanId",
3954
+ )
3955
+ == requested
3956
+ ):
3957
+ return candidates, item
3958
+ raise WalletBackendError(
3959
+ f"Requested obligation_address is not available for Kamino {action} in the selected market."
3960
+ )
3961
+ if len(candidates) == 1:
3962
+ return candidates, candidates[0]
3963
+ return candidates, None
3964
+
3928
3965
  async def _prepare_kamino_lend_transaction(
3929
3966
  self,
3930
3967
  *,
@@ -3933,6 +3970,7 @@ class SolanaWalletBackend(AgentWalletBackend):
3933
3970
  market: str,
3934
3971
  reserve: str,
3935
3972
  amount_ui: str,
3973
+ obligation_address: str | None = None,
3936
3974
  ) -> dict[str, Any]:
3937
3975
  if not self.signer:
3938
3976
  raise WalletBackendError("Solana signer is not configured.")
@@ -3953,12 +3991,49 @@ class SolanaWalletBackend(AgentWalletBackend):
3953
3991
  market_address=market,
3954
3992
  reserve_address=reserve,
3955
3993
  action=f"Kamino {action}",
3994
+ obligation_address=obligation_address,
3956
3995
  loaded_addresses=loaded_addresses,
3957
3996
  )
3958
3997
  signed_transaction_base64 = await self._sign_versioned_provider_transaction(
3959
3998
  transaction_base64=transaction_base64,
3960
3999
  wallet_signer_index=int(verification.get("wallet_signer_index") or 0),
3961
4000
  )
4001
+ simulation_value: dict[str, Any] | None = None
4002
+ kamino_safety: dict[str, Any]
4003
+ try:
4004
+ simulation = await solana_rpc.simulate_transaction(
4005
+ transaction_base64=signed_transaction_base64,
4006
+ rpc_url=self.rpc_urls,
4007
+ commitment=self.commitment,
4008
+ )
4009
+ simulation_value = (
4010
+ simulation.get("value") if isinstance(simulation.get("value"), dict) else {}
4011
+ )
4012
+ if isinstance(simulation_value, dict) and simulation_value.get("err") is not None:
4013
+ raise WalletBackendError(
4014
+ f"Kamino {action} transaction simulation failed.",
4015
+ code="kamino_simulation_failed",
4016
+ details={
4017
+ "simulation": simulation_value,
4018
+ "action": action,
4019
+ "market": market,
4020
+ "reserve": reserve,
4021
+ },
4022
+ )
4023
+ kamino_safety = {
4024
+ "verified": True,
4025
+ "simulation_unavailable": False,
4026
+ }
4027
+ except ProviderError as exc:
4028
+ kamino_safety = {
4029
+ "verified": False,
4030
+ "simulation_unavailable": True,
4031
+ "warning": (
4032
+ "Kamino simulation could not be completed via the configured Solana RPC. "
4033
+ "Proceeding with structural provider verification only."
4034
+ ),
4035
+ "error": str(exc),
4036
+ }
3962
4037
  return {
3963
4038
  "chain": "solana",
3964
4039
  "network": self.network,
@@ -3967,6 +4042,7 @@ class SolanaWalletBackend(AgentWalletBackend):
3967
4042
  "owner": owner,
3968
4043
  "market": market,
3969
4044
  "reserve": reserve,
4045
+ "obligation_address": obligation_address,
3970
4046
  "amount_ui": amount_ui,
3971
4047
  "transaction_base64": signed_transaction_base64,
3972
4048
  "transaction_encoding": "base64",
@@ -3975,15 +4051,30 @@ class SolanaWalletBackend(AgentWalletBackend):
3975
4051
  "broadcasted": False,
3976
4052
  "confirmed": False,
3977
4053
  "verification": verification,
4054
+ "simulation": simulation_value,
4055
+ "kamino_safety": kamino_safety,
3978
4056
  "sign_only": self.sign_only,
3979
4057
  "source": "kamino",
3980
4058
  }
3981
4059
 
4060
+ def _kamino_preview_from_approved(
4061
+ self,
4062
+ approved_preview: dict[str, Any] | None,
4063
+ *,
4064
+ asset_type: str,
4065
+ ) -> dict[str, Any] | None:
4066
+ if not isinstance(approved_preview, dict):
4067
+ return None
4068
+ if str(approved_preview.get("asset_type") or "").strip() != asset_type:
4069
+ return None
4070
+ return dict(approved_preview)
4071
+
3982
4072
  async def preview_kamino_lend_deposit(
3983
4073
  self,
3984
4074
  market: str,
3985
4075
  reserve: str,
3986
4076
  amount_ui: str,
4077
+ obligation_address: str | None = None,
3987
4078
  ) -> dict[str, Any]:
3988
4079
  self._require_mainnet_kamino("Kamino lending")
3989
4080
  owner = await self.get_address()
@@ -4021,8 +4112,13 @@ class SolanaWalletBackend(AgentWalletBackend):
4021
4112
  market: str,
4022
4113
  reserve: str,
4023
4114
  amount_ui: str,
4115
+ obligation_address: str | None = None,
4116
+ approved_preview: dict[str, Any] | None = None,
4024
4117
  ) -> dict[str, Any]:
4025
- preview = await self.preview_kamino_lend_deposit(
4118
+ preview = self._kamino_preview_from_approved(
4119
+ approved_preview,
4120
+ asset_type="kamino-lend-deposit",
4121
+ ) or await self.preview_kamino_lend_deposit(
4026
4122
  market=market,
4027
4123
  reserve=reserve,
4028
4124
  amount_ui=amount_ui,
@@ -4049,11 +4145,14 @@ class SolanaWalletBackend(AgentWalletBackend):
4049
4145
  market: str,
4050
4146
  reserve: str,
4051
4147
  amount_ui: str,
4148
+ obligation_address: str | None = None,
4149
+ approved_preview: dict[str, Any] | None = None,
4052
4150
  ) -> dict[str, Any]:
4053
4151
  prepared = await self.prepare_kamino_lend_deposit(
4054
4152
  market=market,
4055
4153
  reserve=reserve,
4056
4154
  amount_ui=amount_ui,
4155
+ approved_preview=approved_preview,
4057
4156
  )
4058
4157
  result = await self._execute_prepared_provider_transaction(prepared, source="kamino")
4059
4158
  result["build_response"] = prepared.get("build_response")
@@ -4064,6 +4163,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4064
4163
  market: str,
4065
4164
  reserve: str,
4066
4165
  amount_ui: str,
4166
+ obligation_address: str | None = None,
4067
4167
  ) -> dict[str, Any]:
4068
4168
  self._require_mainnet_kamino("Kamino lending")
4069
4169
  owner = await self.get_address()
@@ -4088,6 +4188,19 @@ class SolanaWalletBackend(AgentWalletBackend):
4088
4188
  )
4089
4189
  if not obligation_matches:
4090
4190
  raise WalletBackendError("No Kamino obligation found for the requested reserve.")
4191
+ obligation_options, selected_obligation = self._resolve_kamino_obligation_selection(
4192
+ obligations=obligation_matches,
4193
+ obligation_address=obligation_address,
4194
+ action="withdraw",
4195
+ )
4196
+ selected_obligation_address = _kamino_entry_address(
4197
+ selected_obligation,
4198
+ "obligationAddress",
4199
+ "obligation",
4200
+ "address",
4201
+ "pubkey",
4202
+ "loanId",
4203
+ )
4091
4204
  return {
4092
4205
  "chain": "solana",
4093
4206
  "network": self.network,
@@ -4098,7 +4211,14 @@ class SolanaWalletBackend(AgentWalletBackend):
4098
4211
  "reserve": reserve,
4099
4212
  "amount_ui": amount_ui,
4100
4213
  "reserve_info": reserve_entry,
4101
- "obligations": obligation_matches,
4214
+ "obligations": obligation_options,
4215
+ "obligation_options": [
4216
+ _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4217
+ for item in obligation_options
4218
+ if _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4219
+ ],
4220
+ "obligation_address": selected_obligation_address or None,
4221
+ "requires_obligation_address": selected_obligation is None and len(obligation_options) > 1,
4102
4222
  "sign_only": self.sign_only,
4103
4223
  "can_send": self.get_capabilities().can_send_transaction,
4104
4224
  "source": "kamino",
@@ -4109,12 +4229,23 @@ class SolanaWalletBackend(AgentWalletBackend):
4109
4229
  market: str,
4110
4230
  reserve: str,
4111
4231
  amount_ui: str,
4232
+ obligation_address: str | None = None,
4233
+ approved_preview: dict[str, Any] | None = None,
4112
4234
  ) -> dict[str, Any]:
4113
- preview = await self.preview_kamino_lend_withdraw(
4235
+ preview = self._kamino_preview_from_approved(
4236
+ approved_preview,
4237
+ asset_type="kamino-lend-withdraw",
4238
+ ) or await self.preview_kamino_lend_withdraw(
4114
4239
  market=market,
4115
4240
  reserve=reserve,
4116
4241
  amount_ui=amount_ui,
4242
+ obligation_address=obligation_address,
4117
4243
  )
4244
+ selected_obligation_address = str(preview.get("obligation_address") or "").strip()
4245
+ if bool(preview.get("requires_obligation_address")) and not selected_obligation_address:
4246
+ raise WalletBackendError(
4247
+ "Kamino withdraw requires obligation_address when multiple obligations match the selected market/reserve."
4248
+ )
4118
4249
  owner = str(preview["owner"])
4119
4250
  build = await kamino.build_lend_withdraw_transaction(
4120
4251
  wallet=owner,
@@ -4128,6 +4259,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4128
4259
  market=str(preview["market"]),
4129
4260
  reserve=str(preview["reserve"]),
4130
4261
  amount_ui=str(preview["amount_ui"]),
4262
+ obligation_address=selected_obligation_address or None,
4131
4263
  )
4132
4264
  prepared["build_response"] = build
4133
4265
  return prepared
@@ -4137,11 +4269,15 @@ class SolanaWalletBackend(AgentWalletBackend):
4137
4269
  market: str,
4138
4270
  reserve: str,
4139
4271
  amount_ui: str,
4272
+ obligation_address: str | None = None,
4273
+ approved_preview: dict[str, Any] | None = None,
4140
4274
  ) -> dict[str, Any]:
4141
4275
  prepared = await self.prepare_kamino_lend_withdraw(
4142
4276
  market=market,
4143
4277
  reserve=reserve,
4144
4278
  amount_ui=amount_ui,
4279
+ obligation_address=obligation_address,
4280
+ approved_preview=approved_preview,
4145
4281
  )
4146
4282
  result = await self._execute_prepared_provider_transaction(prepared, source="kamino")
4147
4283
  result["build_response"] = prepared.get("build_response")
@@ -4152,6 +4288,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4152
4288
  market: str,
4153
4289
  reserve: str,
4154
4290
  amount_ui: str,
4291
+ obligation_address: str | None = None,
4155
4292
  ) -> dict[str, Any]:
4156
4293
  self._require_mainnet_kamino("Kamino lending")
4157
4294
  owner = await self.get_address()
@@ -4172,6 +4309,19 @@ class SolanaWalletBackend(AgentWalletBackend):
4172
4309
  obligations = await self.get_kamino_lend_user_obligations(market=market, user=owner)
4173
4310
  if int(obligations["obligation_count"]) <= 0:
4174
4311
  raise WalletBackendError("Kamino borrow requires an existing obligation in the selected market.")
4312
+ obligation_options, selected_obligation = self._resolve_kamino_obligation_selection(
4313
+ obligations=list(obligations["obligations"]),
4314
+ obligation_address=obligation_address,
4315
+ action="borrow",
4316
+ )
4317
+ selected_obligation_address = _kamino_entry_address(
4318
+ selected_obligation,
4319
+ "obligationAddress",
4320
+ "obligation",
4321
+ "address",
4322
+ "pubkey",
4323
+ "loanId",
4324
+ )
4175
4325
  return {
4176
4326
  "chain": "solana",
4177
4327
  "network": self.network,
@@ -4182,7 +4332,14 @@ class SolanaWalletBackend(AgentWalletBackend):
4182
4332
  "reserve": reserve,
4183
4333
  "amount_ui": amount_ui,
4184
4334
  "reserve_info": reserve_entry,
4185
- "obligations": obligations["obligations"],
4335
+ "obligations": obligation_options,
4336
+ "obligation_options": [
4337
+ _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4338
+ for item in obligation_options
4339
+ if _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4340
+ ],
4341
+ "obligation_address": selected_obligation_address or None,
4342
+ "requires_obligation_address": selected_obligation is None and len(obligation_options) > 1,
4186
4343
  "sign_only": self.sign_only,
4187
4344
  "can_send": self.get_capabilities().can_send_transaction,
4188
4345
  "source": "kamino",
@@ -4193,12 +4350,23 @@ class SolanaWalletBackend(AgentWalletBackend):
4193
4350
  market: str,
4194
4351
  reserve: str,
4195
4352
  amount_ui: str,
4353
+ obligation_address: str | None = None,
4354
+ approved_preview: dict[str, Any] | None = None,
4196
4355
  ) -> dict[str, Any]:
4197
- preview = await self.preview_kamino_lend_borrow(
4356
+ preview = self._kamino_preview_from_approved(
4357
+ approved_preview,
4358
+ asset_type="kamino-lend-borrow",
4359
+ ) or await self.preview_kamino_lend_borrow(
4198
4360
  market=market,
4199
4361
  reserve=reserve,
4200
4362
  amount_ui=amount_ui,
4363
+ obligation_address=obligation_address,
4201
4364
  )
4365
+ selected_obligation_address = str(preview.get("obligation_address") or "").strip()
4366
+ if bool(preview.get("requires_obligation_address")) and not selected_obligation_address:
4367
+ raise WalletBackendError(
4368
+ "Kamino borrow requires obligation_address when multiple obligations exist in the selected market."
4369
+ )
4202
4370
  owner = str(preview["owner"])
4203
4371
  build = await kamino.build_lend_borrow_transaction(
4204
4372
  wallet=owner,
@@ -4212,6 +4380,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4212
4380
  market=str(preview["market"]),
4213
4381
  reserve=str(preview["reserve"]),
4214
4382
  amount_ui=str(preview["amount_ui"]),
4383
+ obligation_address=selected_obligation_address or None,
4215
4384
  )
4216
4385
  prepared["build_response"] = build
4217
4386
  return prepared
@@ -4221,11 +4390,15 @@ class SolanaWalletBackend(AgentWalletBackend):
4221
4390
  market: str,
4222
4391
  reserve: str,
4223
4392
  amount_ui: str,
4393
+ obligation_address: str | None = None,
4394
+ approved_preview: dict[str, Any] | None = None,
4224
4395
  ) -> dict[str, Any]:
4225
4396
  prepared = await self.prepare_kamino_lend_borrow(
4226
4397
  market=market,
4227
4398
  reserve=reserve,
4228
4399
  amount_ui=amount_ui,
4400
+ obligation_address=obligation_address,
4401
+ approved_preview=approved_preview,
4229
4402
  )
4230
4403
  result = await self._execute_prepared_provider_transaction(prepared, source="kamino")
4231
4404
  result["build_response"] = prepared.get("build_response")
@@ -4236,6 +4409,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4236
4409
  market: str,
4237
4410
  reserve: str,
4238
4411
  amount_ui: str,
4412
+ obligation_address: str | None = None,
4239
4413
  ) -> dict[str, Any]:
4240
4414
  self._require_mainnet_kamino("Kamino lending")
4241
4415
  owner = await self.get_address()
@@ -4260,6 +4434,19 @@ class SolanaWalletBackend(AgentWalletBackend):
4260
4434
  )
4261
4435
  if not obligation_matches:
4262
4436
  raise WalletBackendError("No Kamino debt position found for the requested reserve.")
4437
+ obligation_options, selected_obligation = self._resolve_kamino_obligation_selection(
4438
+ obligations=obligation_matches,
4439
+ obligation_address=obligation_address,
4440
+ action="repay",
4441
+ )
4442
+ selected_obligation_address = _kamino_entry_address(
4443
+ selected_obligation,
4444
+ "obligationAddress",
4445
+ "obligation",
4446
+ "address",
4447
+ "pubkey",
4448
+ "loanId",
4449
+ )
4263
4450
  return {
4264
4451
  "chain": "solana",
4265
4452
  "network": self.network,
@@ -4270,7 +4457,14 @@ class SolanaWalletBackend(AgentWalletBackend):
4270
4457
  "reserve": reserve,
4271
4458
  "amount_ui": amount_ui,
4272
4459
  "reserve_info": reserve_entry,
4273
- "obligations": obligation_matches,
4460
+ "obligations": obligation_options,
4461
+ "obligation_options": [
4462
+ _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4463
+ for item in obligation_options
4464
+ if _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4465
+ ],
4466
+ "obligation_address": selected_obligation_address or None,
4467
+ "requires_obligation_address": selected_obligation is None and len(obligation_options) > 1,
4274
4468
  "sign_only": self.sign_only,
4275
4469
  "can_send": self.get_capabilities().can_send_transaction,
4276
4470
  "source": "kamino",
@@ -4281,12 +4475,23 @@ class SolanaWalletBackend(AgentWalletBackend):
4281
4475
  market: str,
4282
4476
  reserve: str,
4283
4477
  amount_ui: str,
4478
+ obligation_address: str | None = None,
4479
+ approved_preview: dict[str, Any] | None = None,
4284
4480
  ) -> dict[str, Any]:
4285
- preview = await self.preview_kamino_lend_repay(
4481
+ preview = self._kamino_preview_from_approved(
4482
+ approved_preview,
4483
+ asset_type="kamino-lend-repay",
4484
+ ) or await self.preview_kamino_lend_repay(
4286
4485
  market=market,
4287
4486
  reserve=reserve,
4288
4487
  amount_ui=amount_ui,
4488
+ obligation_address=obligation_address,
4289
4489
  )
4490
+ selected_obligation_address = str(preview.get("obligation_address") or "").strip()
4491
+ if bool(preview.get("requires_obligation_address")) and not selected_obligation_address:
4492
+ raise WalletBackendError(
4493
+ "Kamino repay requires obligation_address when multiple debt obligations match the selected market/reserve."
4494
+ )
4290
4495
  owner = str(preview["owner"])
4291
4496
  build = await kamino.build_lend_repay_transaction(
4292
4497
  wallet=owner,
@@ -4300,6 +4505,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4300
4505
  market=str(preview["market"]),
4301
4506
  reserve=str(preview["reserve"]),
4302
4507
  amount_ui=str(preview["amount_ui"]),
4508
+ obligation_address=selected_obligation_address or None,
4303
4509
  )
4304
4510
  prepared["build_response"] = build
4305
4511
  return prepared
@@ -4309,11 +4515,15 @@ class SolanaWalletBackend(AgentWalletBackend):
4309
4515
  market: str,
4310
4516
  reserve: str,
4311
4517
  amount_ui: str,
4518
+ obligation_address: str | None = None,
4519
+ approved_preview: dict[str, Any] | None = None,
4312
4520
  ) -> dict[str, Any]:
4313
4521
  prepared = await self.prepare_kamino_lend_repay(
4314
4522
  market=market,
4315
4523
  reserve=reserve,
4316
4524
  amount_ui=amount_ui,
4525
+ obligation_address=obligation_address,
4526
+ approved_preview=approved_preview,
4317
4527
  )
4318
4528
  result = await self._execute_prepared_provider_transaction(prepared, source="kamino")
4319
4529
  result["build_response"] = prepared.get("build_response")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openclaw-agent-wallet"
7
- version = "0.1.26"
7
+ version = "0.1.27"
8
8
  description = "Plugin-friendly wallet backend for OpenClaw agents"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -80,6 +80,7 @@ Use this skill before calling OpenClaw wallet tools. It is the routing guide for
80
80
  - Prefer `mode=intent_preview`, show the intent limits to the user, then after chat confirmation call `mode=intent_execute` with the same semantic params. This confirms risk limits, not a stale quote fingerprint.
81
81
  - Default Solana swap slippage is 300 bps (3%). The backend computes the approved minimum output from the indicative output and slippage, not from a strict RFQ threshold.
82
82
  - The primary execution path uses Jupiter Swap API V2 `/order` + `/execute`; if a JupiterZ/RFQ route fails, the backend retries with a non-JupiterZ route when possible.
83
+ - Metis `/swap` fallback builds use Jupiter dynamic slippage and a bounded `veryHigh` priority fee instead of the old `"auto"` priority mode.
83
84
  - Do not use legacy `execute` for Solana Jupiter swaps in OpenClaw; exact quote-bound approval is too fragile for active markets.
84
85
  - Use for SOL<->SPL or SPL<->SPL on Solana. Do not use LI.FI for Solana-only swaps.
85
86
  - EVM same-chain Velora swap: `swap_evm_tokens`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayer.tech/wallet",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "NPM installer for the OpenClaw Agent Wallet local runtime.",
5
5
  "type": "module",
6
6
  "repository": {