@agentlayer.tech/wallet 0.1.44 → 0.1.48

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.
@@ -117,6 +117,7 @@ async def fetch_quote(
117
117
  restrict_intermediate_tokens: bool = True,
118
118
  only_direct_routes: bool = False,
119
119
  swap_mode: str = "ExactIn",
120
+ exclude_dexes: list[str] | None = None,
120
121
  ) -> dict[str, Any]:
121
122
  """Fetch a Jupiter quote for an exact-in swap.
122
123
 
@@ -133,6 +134,7 @@ async def fetch_quote(
133
134
  restrict_intermediate_tokens=restrict_intermediate_tokens,
134
135
  only_direct_routes=only_direct_routes,
135
136
  swap_mode=swap_mode,
137
+ exclude_dexes=exclude_dexes,
136
138
  )
137
139
  except ProviderError as exc:
138
140
  error_msg = str(exc).lower()
@@ -168,6 +170,7 @@ async def _fetch_quote_direct(
168
170
  restrict_intermediate_tokens: bool = True,
169
171
  only_direct_routes: bool = False,
170
172
  swap_mode: str = "ExactIn",
173
+ exclude_dexes: list[str] | None = None,
171
174
  ) -> dict[str, Any]:
172
175
  """Fetch a Jupiter quote directly from Jupiter API."""
173
176
  client = get_client()
@@ -180,6 +183,8 @@ async def _fetch_quote_direct(
180
183
  "restrictIntermediateTokens": str(restrict_intermediate_tokens).lower(),
181
184
  "onlyDirectRoutes": str(only_direct_routes).lower(),
182
185
  }
186
+ if exclude_dexes:
187
+ params["excludeDexes"] = ",".join(str(d).strip() for d in exclude_dexes if str(d).strip())
183
188
  response = await client.get(
184
189
  f"{settings.jupiter_api_base_url.rstrip('/')}/quote",
185
190
  params=params,
@@ -18,6 +18,12 @@ LONG_RUNNING_POST_PATHS = {
18
18
  "/v1/evm/aave/withdraw/send",
19
19
  "/v1/evm/aave/borrow/send",
20
20
  "/v1/evm/aave/repay/send",
21
+ "/v1/evm/morpho/vault/supply/send",
22
+ "/v1/evm/morpho/vault/withdraw/send",
23
+ "/v1/evm/morpho/market/supply_collateral/send",
24
+ "/v1/evm/morpho/market/borrow/send",
25
+ "/v1/evm/morpho/market/repay/send",
26
+ "/v1/evm/morpho/market/withdraw_collateral/send",
21
27
  "/v1/evm/lido/stake_eth_for_wsteth/send",
22
28
  "/v1/evm/lido/wrap_steth/send",
23
29
  "/v1/evm/lido/unwrap_wsteth/send",
@@ -120,6 +120,35 @@ class AgentWalletBackend(ABC):
120
120
  async def get_evm_lido_withdrawal_requests(self) -> dict[str, Any]:
121
121
  raise WalletBackendError(f"{self.name} does not support EVM Lido withdrawal lookup.")
122
122
 
123
+ async def get_evm_morpho_vaults(
124
+ self,
125
+ *,
126
+ vault_address: str | None = None,
127
+ limit: int | None = None,
128
+ listed_only: bool = True,
129
+ asset_address: str | None = None,
130
+ order_by: str | None = None,
131
+ order_direction: str | None = None,
132
+ ) -> dict[str, Any]:
133
+ raise WalletBackendError(f"{self.name} does not support EVM Morpho vault lookup.")
134
+
135
+ async def get_evm_morpho_markets(
136
+ self,
137
+ *,
138
+ market_id: str | None = None,
139
+ limit: int | None = None,
140
+ listed_only: bool = True,
141
+ search: str | None = None,
142
+ collateral_asset_address: str | None = None,
143
+ loan_asset_address: str | None = None,
144
+ order_by: str | None = None,
145
+ order_direction: str | None = None,
146
+ ) -> dict[str, Any]:
147
+ raise WalletBackendError(f"{self.name} does not support EVM Morpho market lookup.")
148
+
149
+ async def get_evm_morpho_positions(self) -> dict[str, Any]:
150
+ raise WalletBackendError(f"{self.name} does not support EVM Morpho positions lookup.")
151
+
123
152
  async def preview_evm_aave_operation(
124
153
  self,
125
154
  *,
@@ -175,6 +204,56 @@ class AgentWalletBackend(ABC):
175
204
  ) -> dict[str, Any]:
176
205
  raise WalletBackendError(f"{self.name} does not support EVM Lido withdrawals.")
177
206
 
207
+ async def preview_evm_morpho_vault_operation(
208
+ self,
209
+ *,
210
+ operation: str,
211
+ token_address: str,
212
+ vault_address: str | None = None,
213
+ vault_preset: str | None = None,
214
+ amount_raw: str | None = None,
215
+ native_amount_raw: str | None = None,
216
+ ) -> dict[str, Any]:
217
+ raise WalletBackendError(f"{self.name} does not support EVM Morpho vault previews.")
218
+
219
+ async def send_evm_morpho_vault_operation(
220
+ self,
221
+ *,
222
+ operation: str,
223
+ token_address: str,
224
+ vault_address: str | None = None,
225
+ vault_preset: str | None = None,
226
+ amount_raw: str | None = None,
227
+ native_amount_raw: str | None = None,
228
+ expected_quote_fingerprint: str | None = None,
229
+ ) -> dict[str, Any]:
230
+ raise WalletBackendError(f"{self.name} does not support EVM Morpho vault operations.")
231
+
232
+ async def preview_evm_morpho_market_operation(
233
+ self,
234
+ *,
235
+ operation: str,
236
+ token_address: str,
237
+ market_id: str | None = None,
238
+ market_preset: str | None = None,
239
+ amount_raw: str | None = None,
240
+ native_amount_raw: str | None = None,
241
+ ) -> dict[str, Any]:
242
+ raise WalletBackendError(f"{self.name} does not support EVM Morpho market previews.")
243
+
244
+ async def send_evm_morpho_market_operation(
245
+ self,
246
+ *,
247
+ operation: str,
248
+ token_address: str,
249
+ market_id: str | None = None,
250
+ market_preset: str | None = None,
251
+ amount_raw: str | None = None,
252
+ native_amount_raw: str | None = None,
253
+ expected_quote_fingerprint: str | None = None,
254
+ ) -> dict[str, Any]:
255
+ raise WalletBackendError(f"{self.name} does not support EVM Morpho market operations.")
256
+
178
257
  async def preview_evm_swap(
179
258
  self,
180
259
  *,
@@ -524,6 +603,116 @@ class AgentWalletBackend(ABC):
524
603
  ) -> dict[str, Any]:
525
604
  raise WalletBackendError(f"{self.name} does not support Kamino withdraw previews.")
526
605
 
606
+ def _build_kamino_lend_intent_preview(
607
+ self,
608
+ base_preview: dict[str, Any],
609
+ *,
610
+ valid_for_seconds: int,
611
+ ) -> dict[str, Any]:
612
+ """Wrap a base Kamino lend preview into an intent approval preview.
613
+
614
+ The intent binds approval to stable semantic parameters (owner, market,
615
+ reserve, amount, obligation) instead of an ephemeral preview digest, so
616
+ execute can re-derive the transaction server-side without the host having
617
+ to round-trip the full preview payload back as _approved_preview.
618
+ """
619
+ if valid_for_seconds <= 0 or valid_for_seconds > 300:
620
+ raise WalletBackendError("valid_for_seconds must be between 1 and 300.")
621
+ try:
622
+ can_send = bool(self.get_capabilities().can_send_transaction)
623
+ except Exception:
624
+ can_send = bool(base_preview.get("can_send"))
625
+ return {
626
+ "chain": "solana",
627
+ "network": getattr(self, "network", "mainnet"),
628
+ "mode": "intent_preview",
629
+ "asset_type": "kamino-lend-intent",
630
+ "kamino_operation": base_preview["asset_type"],
631
+ "owner": base_preview["owner"],
632
+ "market": base_preview["market"],
633
+ "reserve": base_preview["reserve"],
634
+ "amount_ui": base_preview["amount_ui"],
635
+ "obligation_address": base_preview.get("obligation_address"),
636
+ "obligation_options": base_preview.get("obligation_options", []),
637
+ "requires_obligation_address": bool(base_preview.get("requires_obligation_address")),
638
+ "reserve_info": base_preview.get("reserve_info"),
639
+ "recipient_policy": "owner-only",
640
+ "spend_policy": "exact-amount",
641
+ "valid_for_seconds": valid_for_seconds,
642
+ "valid_until_epoch_seconds": int(time.time()) + valid_for_seconds,
643
+ "intent_note": (
644
+ "This is an intent approval preview. Execute re-derives the Kamino "
645
+ "transaction and only signs/sends if it remains within these approved parameters."
646
+ ),
647
+ "can_send": can_send,
648
+ "sign_only": bool(getattr(self, "sign_only", False)),
649
+ "source": "kamino-intent",
650
+ }
651
+
652
+ async def preview_kamino_lend_deposit_intent(
653
+ self,
654
+ market: str,
655
+ reserve: str,
656
+ amount_ui: str,
657
+ obligation_address: str | None = None,
658
+ valid_for_seconds: int = 120,
659
+ ) -> dict[str, Any]:
660
+ base = await self.preview_kamino_lend_deposit(
661
+ market=market,
662
+ reserve=reserve,
663
+ amount_ui=amount_ui,
664
+ obligation_address=obligation_address,
665
+ )
666
+ return self._build_kamino_lend_intent_preview(base, valid_for_seconds=valid_for_seconds)
667
+
668
+ async def preview_kamino_lend_withdraw_intent(
669
+ self,
670
+ market: str,
671
+ reserve: str,
672
+ amount_ui: str,
673
+ obligation_address: str | None = None,
674
+ valid_for_seconds: int = 120,
675
+ ) -> dict[str, Any]:
676
+ base = await self.preview_kamino_lend_withdraw(
677
+ market=market,
678
+ reserve=reserve,
679
+ amount_ui=amount_ui,
680
+ obligation_address=obligation_address,
681
+ )
682
+ return self._build_kamino_lend_intent_preview(base, valid_for_seconds=valid_for_seconds)
683
+
684
+ async def preview_kamino_lend_borrow_intent(
685
+ self,
686
+ market: str,
687
+ reserve: str,
688
+ amount_ui: str,
689
+ obligation_address: str | None = None,
690
+ valid_for_seconds: int = 120,
691
+ ) -> dict[str, Any]:
692
+ base = await self.preview_kamino_lend_borrow(
693
+ market=market,
694
+ reserve=reserve,
695
+ amount_ui=amount_ui,
696
+ obligation_address=obligation_address,
697
+ )
698
+ return self._build_kamino_lend_intent_preview(base, valid_for_seconds=valid_for_seconds)
699
+
700
+ async def preview_kamino_lend_repay_intent(
701
+ self,
702
+ market: str,
703
+ reserve: str,
704
+ amount_ui: str,
705
+ obligation_address: str | None = None,
706
+ valid_for_seconds: int = 120,
707
+ ) -> dict[str, Any]:
708
+ base = await self.preview_kamino_lend_repay(
709
+ market=market,
710
+ reserve=reserve,
711
+ amount_ui=amount_ui,
712
+ obligation_address=obligation_address,
713
+ )
714
+ return self._build_kamino_lend_intent_preview(base, valid_for_seconds=valid_for_seconds)
715
+
527
716
  async def prepare_kamino_lend_withdraw(
528
717
  self,
529
718
  market: str,
@@ -4704,6 +4704,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4704
4704
  amount_ui: float,
4705
4705
  slippage_bps: int = SOLANA_SWAP_DEFAULT_SLIPPAGE_BPS,
4706
4706
  exclude_routers: list[str] | None = None,
4707
+ exclude_dexes: list[str] | None = None,
4707
4708
  ) -> dict[str, Any]:
4708
4709
  if self.network != "mainnet":
4709
4710
  raise WalletBackendError("Provider-routed swaps are only enabled for Solana mainnet.")
@@ -4749,6 +4750,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4749
4750
  output_mint=output_mint,
4750
4751
  amount_raw=raw_amount,
4751
4752
  slippage_bps=slippage_bps,
4753
+ exclude_dexes=exclude_dexes,
4752
4754
  )
4753
4755
  quote_source = "jupiter-metis"
4754
4756
 
@@ -5006,17 +5008,27 @@ class SolanaWalletBackend(AgentWalletBackend):
5006
5008
 
5007
5009
  attempts: list[dict[str, Any]] = []
5008
5010
  last_error: str | None = None
5011
+ _simulation_failed = False
5009
5012
  for attempt_index in range(max_attempts):
5010
5013
  if valid_until_epoch_seconds is not None and int(time.time()) > int(valid_until_epoch_seconds):
5011
5014
  break
5012
5015
  try:
5013
5016
  exclude_routers = ["jupiterz"] if attempt_index > 0 else None
5017
+ # On retries after a simulation failure, exclude DEXes known to fail
5018
+ # simulation for Token-2022 tokens with extensions such as
5019
+ # scaledUiAmountConfig, pausableConfig, or permanentDelegate
5020
+ # (e.g. Backpack xStock tokens). GoonFi V2 is the primary offender;
5021
+ # ZeroFi handles these tokens correctly.
5022
+ exclude_dexes: list[str] | None = None
5023
+ if _simulation_failed and attempt_index > 0:
5024
+ exclude_dexes = ["GoonFi V2"]
5014
5025
  preview = await self.preview_swap(
5015
5026
  input_mint=input_mint,
5016
5027
  output_mint=output_mint,
5017
5028
  amount_ui=amount_ui,
5018
5029
  slippage_bps=slippage_bps,
5019
5030
  exclude_routers=exclude_routers,
5031
+ exclude_dexes=exclude_dexes,
5020
5032
  )
5021
5033
  estimated_output_raw = int(preview.get("estimated_output_amount_raw") or 0)
5022
5034
  if (
@@ -5073,6 +5085,8 @@ class SolanaWalletBackend(AgentWalletBackend):
5073
5085
  return result
5074
5086
  except (WalletBackendError, ProviderError) as exc:
5075
5087
  last_error = str(exc)
5088
+ if "simulation failed" in str(exc).lower():
5089
+ _simulation_failed = True
5076
5090
  attempts.append(
5077
5091
  {
5078
5092
  "attempt": attempt_index + 1,