@agentlayer.tech/wallet 0.1.15 → 0.1.17

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.
@@ -11,7 +11,7 @@ from decimal import Decimal, InvalidOperation
11
11
  from typing import Any
12
12
 
13
13
  from agent_wallet.models import AgentWalletCapabilities, SolanaWalletState
14
- from agent_wallet.providers import bags, houdini, jupiter, kamino, lifi, solana_rpc
14
+ from agent_wallet.providers import bags, flash, flash_sdk_bridge, houdini, jupiter, kamino, lifi, solana_rpc
15
15
  from agent_wallet.solana_stake import (
16
16
  STAKE_STATE_V2_SIZE,
17
17
  deactivate_stake as build_deactivate_stake_instruction,
@@ -26,6 +26,7 @@ from agent_wallet.solana_tx import (
26
26
  )
27
27
  from agent_wallet.transaction_policy import (
28
28
  verify_provider_bags_transaction,
29
+ verify_provider_flash_transaction,
29
30
  verify_provider_kamino_lend_transaction,
30
31
  verify_provider_lend_transaction,
31
32
  verify_provider_swap_simulation_result,
@@ -155,6 +156,21 @@ def _require_positive_decimal_string(value: Any, *, field_name: str) -> str:
155
156
  return normalized or "0"
156
157
 
157
158
 
159
+ def _normalize_flash_symbol(value: Any, *, field_name: str) -> str:
160
+ if not isinstance(value, str) or not value.strip():
161
+ raise WalletBackendError(f"{field_name} must be a non-empty string.")
162
+ return value.strip().upper()
163
+
164
+
165
+ def _normalize_flash_side(value: Any) -> str:
166
+ if not isinstance(value, str) or not value.strip():
167
+ raise WalletBackendError("side must be a non-empty string.")
168
+ normalized = value.strip().lower()
169
+ if normalized not in {"long", "short"}:
170
+ raise WalletBackendError("side must be 'long' or 'short'.")
171
+ return normalized
172
+
173
+
158
174
  def _coerce_positive_int_from_any(value: Any) -> int | None:
159
175
  if value is None or value == "":
160
176
  return None
@@ -2338,6 +2354,10 @@ class SolanaWalletBackend(AgentWalletBackend):
2338
2354
  if self.network != "mainnet":
2339
2355
  raise WalletBackendError(f"{feature} is only enabled for Solana mainnet.")
2340
2356
 
2357
+ def _require_mainnet_flash(self, feature: str) -> None:
2358
+ if self.network != "mainnet":
2359
+ raise WalletBackendError(f"{feature} is only enabled for Solana mainnet.")
2360
+
2341
2361
  def _require_mainnet_kamino(self, feature: str) -> None:
2342
2362
  if self.network != "mainnet":
2343
2363
  raise WalletBackendError(f"{feature} is only enabled for Solana mainnet.")
@@ -2481,6 +2501,608 @@ class SolanaWalletBackend(AgentWalletBackend):
2481
2501
  "source": "jupiter-lend",
2482
2502
  }
2483
2503
 
2504
+ async def get_flash_trade_markets(
2505
+ self,
2506
+ pool_name: str | None = None,
2507
+ ) -> dict[str, Any]:
2508
+ self._require_mainnet_flash("Flash Trade")
2509
+ normalized_pool_name: str | None = None
2510
+ if pool_name is not None:
2511
+ if not isinstance(pool_name, str) or not pool_name.strip():
2512
+ raise WalletBackendError("pool_name must be a non-empty string when provided.")
2513
+ normalized_pool_name = pool_name.strip()
2514
+ try:
2515
+ data = await flash.fetch_markets(pool_name=normalized_pool_name)
2516
+ except ProviderError:
2517
+ data = await flash_sdk_bridge.get_markets(
2518
+ pool_name=normalized_pool_name,
2519
+ network=self.network,
2520
+ )
2521
+ markets = data.get("markets")
2522
+ if not isinstance(markets, list):
2523
+ markets = []
2524
+ return {
2525
+ "chain": "solana",
2526
+ "network": self.network,
2527
+ "pool_name": normalized_pool_name,
2528
+ "market_count": len(markets),
2529
+ "markets": markets,
2530
+ "raw": data,
2531
+ "source": str(data.get("source") or "flash-trade"),
2532
+ }
2533
+
2534
+ async def get_flash_trade_positions(
2535
+ self,
2536
+ owner: str | None = None,
2537
+ pool_name: str | None = None,
2538
+ ) -> dict[str, Any]:
2539
+ self._require_mainnet_flash("Flash Trade")
2540
+ wallet_address = owner or self.address
2541
+ if not wallet_address:
2542
+ raise WalletBackendError(
2543
+ "A wallet address is required for Flash Trade position lookup."
2544
+ )
2545
+ wallet_address = validate_solana_address(wallet_address)
2546
+ normalized_pool_name: str | None = None
2547
+ if pool_name is not None:
2548
+ if not isinstance(pool_name, str) or not pool_name.strip():
2549
+ raise WalletBackendError("pool_name must be a non-empty string when provided.")
2550
+ normalized_pool_name = pool_name.strip()
2551
+ try:
2552
+ data = await flash.fetch_positions(
2553
+ owner=wallet_address,
2554
+ pool_name=normalized_pool_name,
2555
+ )
2556
+ except ProviderError:
2557
+ data = await flash_sdk_bridge.get_positions(
2558
+ owner=wallet_address,
2559
+ pool_name=normalized_pool_name,
2560
+ network=self.network,
2561
+ )
2562
+ positions = data.get("positions")
2563
+ if not isinstance(positions, list):
2564
+ positions = []
2565
+ return {
2566
+ "chain": "solana",
2567
+ "network": self.network,
2568
+ "owner": wallet_address,
2569
+ "pool_name": normalized_pool_name,
2570
+ "position_count": len(positions),
2571
+ "positions": positions,
2572
+ "raw": data,
2573
+ "source": str(data.get("source") or "flash-trade"),
2574
+ }
2575
+
2576
+ async def preview_flash_trade_open_position(
2577
+ self,
2578
+ *,
2579
+ pool_name: str,
2580
+ market_symbol: str,
2581
+ collateral_symbol: str,
2582
+ collateral_amount_raw: str,
2583
+ leverage: str,
2584
+ side: str,
2585
+ ) -> dict[str, Any]:
2586
+ self._require_mainnet_flash("Flash Trade")
2587
+ owner = await self.get_address()
2588
+ if not owner:
2589
+ raise WalletBackendError(
2590
+ "No Solana wallet address configured. Set SOLANA_AGENT_PUBLIC_KEY or a signer."
2591
+ )
2592
+ normalized_pool_name = str(pool_name).strip()
2593
+ if not normalized_pool_name:
2594
+ raise WalletBackendError("pool_name is required.")
2595
+ normalized_market_symbol = _normalize_flash_symbol(
2596
+ market_symbol,
2597
+ field_name="market_symbol",
2598
+ )
2599
+ normalized_collateral_symbol = _normalize_flash_symbol(
2600
+ collateral_symbol,
2601
+ field_name="collateral_symbol",
2602
+ )
2603
+ normalized_collateral_amount_raw = _require_positive_integer_string(
2604
+ collateral_amount_raw,
2605
+ field_name="collateral_amount_raw",
2606
+ )
2607
+ normalized_leverage = _require_positive_decimal_string(leverage, field_name="leverage")
2608
+ normalized_side = _normalize_flash_side(side)
2609
+ market_snapshot = await self.get_flash_trade_markets(pool_name=normalized_pool_name)
2610
+ matching_market = next(
2611
+ (
2612
+ item
2613
+ for item in market_snapshot["markets"]
2614
+ if isinstance(item, dict)
2615
+ and str(item.get("market_symbol") or item.get("symbol") or "").strip().upper()
2616
+ == normalized_market_symbol
2617
+ and str(item.get("side") or "").strip().lower() == normalized_side
2618
+ and str(item.get("collateral_symbol") or "").strip().upper()
2619
+ == normalized_collateral_symbol
2620
+ ),
2621
+ None,
2622
+ )
2623
+ if matching_market is None:
2624
+ raise WalletBackendError(
2625
+ "Requested Flash market is not available in the selected pool for the requested collateral and side."
2626
+ )
2627
+ bridge_preview = await flash_sdk_bridge.preview_open_position(
2628
+ owner=owner,
2629
+ pool_name=normalized_pool_name,
2630
+ market_symbol=normalized_market_symbol,
2631
+ collateral_symbol=normalized_collateral_symbol,
2632
+ collateral_amount_raw=normalized_collateral_amount_raw,
2633
+ leverage=normalized_leverage,
2634
+ side=normalized_side,
2635
+ network=self.network,
2636
+ )
2637
+ return {
2638
+ "chain": "solana",
2639
+ "network": self.network,
2640
+ "mode": "preview",
2641
+ "asset_type": "flash-trade-open-position",
2642
+ "owner": owner,
2643
+ "pool_name": normalized_pool_name,
2644
+ "market_symbol": normalized_market_symbol,
2645
+ "collateral_symbol": normalized_collateral_symbol,
2646
+ "collateral_amount_raw": normalized_collateral_amount_raw,
2647
+ "leverage": normalized_leverage,
2648
+ "side": normalized_side,
2649
+ "market": matching_market,
2650
+ "sign_only": self.sign_only,
2651
+ "can_send": self.get_capabilities().can_send_transaction,
2652
+ "source": "flash-sdk-bridge",
2653
+ **bridge_preview,
2654
+ }
2655
+
2656
+ async def preview_flash_trade_close_position(
2657
+ self,
2658
+ *,
2659
+ pool_name: str,
2660
+ market_symbol: str,
2661
+ side: str,
2662
+ ) -> dict[str, Any]:
2663
+ self._require_mainnet_flash("Flash Trade")
2664
+ owner = await self.get_address()
2665
+ if not owner:
2666
+ raise WalletBackendError(
2667
+ "No Solana wallet address configured. Set SOLANA_AGENT_PUBLIC_KEY or a signer."
2668
+ )
2669
+ normalized_pool_name = str(pool_name).strip()
2670
+ if not normalized_pool_name:
2671
+ raise WalletBackendError("pool_name is required.")
2672
+ normalized_market_symbol = _normalize_flash_symbol(
2673
+ market_symbol,
2674
+ field_name="market_symbol",
2675
+ )
2676
+ normalized_side = _normalize_flash_side(side)
2677
+ positions_snapshot = await self.get_flash_trade_positions(
2678
+ owner=owner,
2679
+ pool_name=normalized_pool_name,
2680
+ )
2681
+ matching_position = next(
2682
+ (
2683
+ item
2684
+ for item in positions_snapshot["positions"]
2685
+ if isinstance(item, dict)
2686
+ and str(item.get("symbol") or item.get("marketSymbol") or "").strip().upper()
2687
+ == normalized_market_symbol
2688
+ and str(item.get("side") or "").strip().lower() == normalized_side
2689
+ ),
2690
+ None,
2691
+ )
2692
+ if matching_position is None:
2693
+ raise WalletBackendError(
2694
+ "No matching Flash position was found for the selected market, side, and pool."
2695
+ )
2696
+ bridge_preview = await flash_sdk_bridge.preview_close_position_same_collateral(
2697
+ owner=owner,
2698
+ pool_name=normalized_pool_name,
2699
+ market_symbol=normalized_market_symbol,
2700
+ side=normalized_side,
2701
+ network=self.network,
2702
+ )
2703
+ return {
2704
+ "chain": "solana",
2705
+ "network": self.network,
2706
+ "mode": "preview",
2707
+ "asset_type": "flash-trade-close-position",
2708
+ "owner": owner,
2709
+ "pool_name": normalized_pool_name,
2710
+ "market_symbol": normalized_market_symbol,
2711
+ "side": normalized_side,
2712
+ "position": matching_position,
2713
+ "sign_only": self.sign_only,
2714
+ "can_send": self.get_capabilities().can_send_transaction,
2715
+ "source": "flash-sdk-bridge",
2716
+ **bridge_preview,
2717
+ }
2718
+
2719
+ async def _prepare_flash_trade_transaction(
2720
+ self,
2721
+ *,
2722
+ preview: dict[str, Any],
2723
+ bridge_prepared: dict[str, Any],
2724
+ action: str,
2725
+ asset_type: str,
2726
+ ) -> dict[str, Any]:
2727
+ bridge_mode = str(bridge_prepared.get("bridge_mode") or "").strip().lower()
2728
+ if bridge_mode == "mock":
2729
+ prepared = {
2730
+ "chain": "solana",
2731
+ "network": self.network,
2732
+ "mode": "prepare",
2733
+ "asset_type": asset_type,
2734
+ "owner": preview.get("owner"),
2735
+ "pool_name": preview.get("pool_name"),
2736
+ "market_symbol": preview.get("market_symbol"),
2737
+ "collateral_symbol": preview.get("collateral_symbol"),
2738
+ "collateral_amount_raw": preview.get("collateral_amount_raw"),
2739
+ "leverage": preview.get("leverage"),
2740
+ "side": preview.get("side"),
2741
+ "signed": False,
2742
+ "broadcasted": False,
2743
+ "confirmed": False,
2744
+ "sign_only": self.sign_only,
2745
+ "source": "flash-sdk-bridge",
2746
+ "bridge_mode": "mock",
2747
+ "mock_prepare_only": True,
2748
+ "mock_warning": (
2749
+ f"{action} is running in FLASH_SDK_BRIDGE_MODE=mock. "
2750
+ "Prepare returns a dry-run execution plan only; execute is disabled until the bridge runs in real mode."
2751
+ ),
2752
+ "build_response": bridge_prepared,
2753
+ }
2754
+ for key in (
2755
+ "estimated_size_usd",
2756
+ "estimated_size_amount_raw",
2757
+ "estimated_collateral_usd",
2758
+ "estimated_collateral_amount_raw",
2759
+ "estimated_entry_price",
2760
+ "estimated_liquidation_price",
2761
+ "estimated_entry_fee_usd",
2762
+ "estimated_total_fee_usd",
2763
+ "estimated_fee_rate_bps",
2764
+ "estimated_available_liquidity_usd",
2765
+ "estimated_borrow_fee_rate",
2766
+ "position_size_usd",
2767
+ "position_size_amount_raw",
2768
+ "close_amount_raw",
2769
+ "estimated_receive_amount_usd",
2770
+ "estimated_mark_price",
2771
+ "estimated_existing_liquidation_price",
2772
+ "estimated_new_liquidation_price",
2773
+ "estimated_profit_usd",
2774
+ "estimated_loss_usd",
2775
+ "estimated_settled_pnl_usd",
2776
+ "estimated_exit_fee_usd",
2777
+ "estimated_total_fees_usd",
2778
+ "estimated_existing_leverage",
2779
+ "estimated_new_leverage",
2780
+ "is_profitable",
2781
+ "is_solvent",
2782
+ "is_partial_close",
2783
+ "position_pubkey",
2784
+ ):
2785
+ if key in preview:
2786
+ prepared[key] = preview[key]
2787
+ return prepared
2788
+
2789
+ if not self.signer:
2790
+ raise WalletBackendError("Solana signer is not configured.")
2791
+ try:
2792
+ from solders.transaction import VersionedTransaction
2793
+ except ImportError as exc:
2794
+ raise WalletBackendError(
2795
+ "solana and solders packages are required for Flash Trade transaction signing."
2796
+ ) from exc
2797
+
2798
+ owner = str(preview.get("owner") or await self.get_address() or "").strip()
2799
+ if not owner:
2800
+ raise WalletBackendError(
2801
+ "No Solana wallet address configured. Set SOLANA_AGENT_PUBLIC_KEY or a signer."
2802
+ )
2803
+ transaction_base64 = str(bridge_prepared.get("transaction_base64") or "").strip()
2804
+ if not transaction_base64:
2805
+ raise WalletBackendError(f"{action} bridge response is missing transaction_base64.")
2806
+
2807
+ unsigned_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_base64))
2808
+ loaded_addresses = await self._resolve_versioned_message_lookup_addresses(
2809
+ unsigned_transaction.message
2810
+ )
2811
+ market_address = validate_solana_address(str(bridge_prepared.get("market_address") or ""))
2812
+ target_custody_address = validate_solana_address(
2813
+ str(bridge_prepared.get("target_custody_address") or "")
2814
+ )
2815
+ collateral_custody_address = validate_solana_address(
2816
+ str(bridge_prepared.get("collateral_custody_address") or "")
2817
+ )
2818
+ position_address_raw = str(bridge_prepared.get("position_address") or "").strip() or None
2819
+ collateral_mint_raw = str(bridge_prepared.get("collateral_mint") or "").strip() or None
2820
+ expected_program_ids_raw = bridge_prepared.get("expected_program_ids")
2821
+ if not isinstance(expected_program_ids_raw, list) or not expected_program_ids_raw:
2822
+ raise WalletBackendError(f"{action} bridge response is missing expected_program_ids.")
2823
+ expected_program_ids = [
2824
+ validate_solana_address(str(value))
2825
+ for value in expected_program_ids_raw
2826
+ if str(value).strip()
2827
+ ]
2828
+ verification = verify_provider_flash_transaction(
2829
+ unsigned_transaction.message,
2830
+ wallet_address=owner,
2831
+ market_address=market_address,
2832
+ target_custody_address=target_custody_address,
2833
+ collateral_custody_address=collateral_custody_address,
2834
+ action=action,
2835
+ expected_program_ids=expected_program_ids,
2836
+ position_address=(
2837
+ validate_solana_address(position_address_raw)
2838
+ if position_address_raw is not None
2839
+ else None
2840
+ ),
2841
+ collateral_mint=(
2842
+ validate_solana_mint(collateral_mint_raw)
2843
+ if collateral_mint_raw is not None
2844
+ else None
2845
+ ),
2846
+ loaded_addresses=loaded_addresses,
2847
+ )
2848
+ signed_transaction_base64 = await self._sign_versioned_provider_transaction(
2849
+ transaction_base64=transaction_base64,
2850
+ wallet_signer_index=int(verification.get("wallet_signer_index") or 0),
2851
+ )
2852
+ prepared = {
2853
+ "chain": "solana",
2854
+ "network": self.network,
2855
+ "mode": "prepare",
2856
+ "asset_type": asset_type,
2857
+ "owner": owner,
2858
+ "pool_name": preview.get("pool_name"),
2859
+ "market_symbol": preview.get("market_symbol"),
2860
+ "collateral_symbol": preview.get("collateral_symbol"),
2861
+ "collateral_amount_raw": preview.get("collateral_amount_raw"),
2862
+ "leverage": preview.get("leverage"),
2863
+ "side": preview.get("side"),
2864
+ "transaction_base64": signed_transaction_base64,
2865
+ "transaction_encoding": str(bridge_prepared.get("transaction_encoding") or "base64"),
2866
+ "transaction_format": str(bridge_prepared.get("transaction_format") or "versioned"),
2867
+ "last_valid_block_height": bridge_prepared.get("last_valid_block_height"),
2868
+ "latest_blockhash": bridge_prepared.get("latest_blockhash"),
2869
+ "signed": True,
2870
+ "broadcasted": False,
2871
+ "confirmed": False,
2872
+ "verification": verification,
2873
+ "sign_only": self.sign_only,
2874
+ "source": "flash-sdk-bridge",
2875
+ "build_response": bridge_prepared,
2876
+ }
2877
+ for key in (
2878
+ "estimated_size_usd",
2879
+ "estimated_size_amount_raw",
2880
+ "estimated_collateral_usd",
2881
+ "estimated_collateral_amount_raw",
2882
+ "estimated_entry_price",
2883
+ "estimated_liquidation_price",
2884
+ "estimated_entry_fee_usd",
2885
+ "estimated_total_fee_usd",
2886
+ "estimated_fee_rate_bps",
2887
+ "estimated_available_liquidity_usd",
2888
+ "estimated_borrow_fee_rate",
2889
+ "position_size_usd",
2890
+ "position_size_amount_raw",
2891
+ "close_amount_raw",
2892
+ "estimated_receive_amount_usd",
2893
+ "estimated_mark_price",
2894
+ "estimated_existing_liquidation_price",
2895
+ "estimated_new_liquidation_price",
2896
+ "estimated_profit_usd",
2897
+ "estimated_loss_usd",
2898
+ "estimated_settled_pnl_usd",
2899
+ "estimated_exit_fee_usd",
2900
+ "estimated_total_fees_usd",
2901
+ "estimated_existing_leverage",
2902
+ "estimated_new_leverage",
2903
+ "is_profitable",
2904
+ "is_solvent",
2905
+ "is_partial_close",
2906
+ "position_pubkey",
2907
+ ):
2908
+ if key in preview:
2909
+ prepared[key] = preview[key]
2910
+ return prepared
2911
+
2912
+ async def prepare_flash_trade_open_position(
2913
+ self,
2914
+ *,
2915
+ pool_name: str,
2916
+ market_symbol: str,
2917
+ collateral_symbol: str,
2918
+ collateral_amount_raw: str,
2919
+ leverage: str,
2920
+ side: str,
2921
+ ) -> dict[str, Any]:
2922
+ preview = await self.preview_flash_trade_open_position(
2923
+ pool_name=pool_name,
2924
+ market_symbol=market_symbol,
2925
+ collateral_symbol=collateral_symbol,
2926
+ collateral_amount_raw=collateral_amount_raw,
2927
+ leverage=leverage,
2928
+ side=side,
2929
+ )
2930
+ bridge_prepared = await flash_sdk_bridge.prepare_open_position(
2931
+ owner=str(preview["owner"]),
2932
+ pool_name=str(preview["pool_name"]),
2933
+ market_symbol=str(preview["market_symbol"]),
2934
+ collateral_symbol=str(preview["collateral_symbol"]),
2935
+ collateral_amount_raw=str(preview["collateral_amount_raw"]),
2936
+ leverage=str(preview["leverage"]),
2937
+ side=str(preview["side"]),
2938
+ network=self.network,
2939
+ )
2940
+ return await self._prepare_flash_trade_transaction(
2941
+ preview=preview,
2942
+ bridge_prepared=bridge_prepared,
2943
+ action="Flash Trade open position",
2944
+ asset_type="flash-trade-open-position",
2945
+ )
2946
+
2947
+ async def _prepare_flash_trade_open_position_from_preview(
2948
+ self,
2949
+ preview: dict[str, Any],
2950
+ ) -> dict[str, Any]:
2951
+ bridge_prepared = await flash_sdk_bridge.prepare_open_position(
2952
+ owner=str(preview["owner"]),
2953
+ pool_name=str(preview["pool_name"]),
2954
+ market_symbol=str(preview["market_symbol"]),
2955
+ collateral_symbol=str(preview["collateral_symbol"]),
2956
+ collateral_amount_raw=str(preview["collateral_amount_raw"]),
2957
+ leverage=str(preview["leverage"]),
2958
+ side=str(preview["side"]),
2959
+ network=self.network,
2960
+ )
2961
+ return await self._prepare_flash_trade_transaction(
2962
+ preview=preview,
2963
+ bridge_prepared=bridge_prepared,
2964
+ action="Flash Trade open position",
2965
+ asset_type="flash-trade-open-position",
2966
+ )
2967
+
2968
+ async def prepare_flash_trade_close_position(
2969
+ self,
2970
+ *,
2971
+ pool_name: str,
2972
+ market_symbol: str,
2973
+ side: str,
2974
+ ) -> dict[str, Any]:
2975
+ preview = await self.preview_flash_trade_close_position(
2976
+ pool_name=pool_name,
2977
+ market_symbol=market_symbol,
2978
+ side=side,
2979
+ )
2980
+ bridge_prepared = await flash_sdk_bridge.prepare_close_position_same_collateral(
2981
+ owner=str(preview["owner"]),
2982
+ pool_name=str(preview["pool_name"]),
2983
+ market_symbol=str(preview["market_symbol"]),
2984
+ side=str(preview["side"]),
2985
+ network=self.network,
2986
+ )
2987
+ return await self._prepare_flash_trade_transaction(
2988
+ preview=preview,
2989
+ bridge_prepared=bridge_prepared,
2990
+ action="Flash Trade close position",
2991
+ asset_type="flash-trade-close-position",
2992
+ )
2993
+
2994
+ async def _prepare_flash_trade_close_position_from_preview(
2995
+ self,
2996
+ preview: dict[str, Any],
2997
+ ) -> dict[str, Any]:
2998
+ bridge_prepared = await flash_sdk_bridge.prepare_close_position_same_collateral(
2999
+ owner=str(preview["owner"]),
3000
+ pool_name=str(preview["pool_name"]),
3001
+ market_symbol=str(preview["market_symbol"]),
3002
+ side=str(preview["side"]),
3003
+ network=self.network,
3004
+ )
3005
+ return await self._prepare_flash_trade_transaction(
3006
+ preview=preview,
3007
+ bridge_prepared=bridge_prepared,
3008
+ action="Flash Trade close position",
3009
+ asset_type="flash-trade-close-position",
3010
+ )
3011
+
3012
+ async def _execute_prepared_flash_trade_transaction(
3013
+ self,
3014
+ prepared: dict[str, Any],
3015
+ ) -> dict[str, Any]:
3016
+ if str(prepared.get("bridge_mode") or "").strip().lower() == "mock":
3017
+ raise WalletBackendError(
3018
+ "Flash Trade execute is unavailable while FLASH_SDK_BRIDGE_MODE=mock. Switch the Flash SDK bridge to real mode first."
3019
+ )
3020
+ result = await self._execute_prepared_provider_transaction(
3021
+ prepared,
3022
+ source="flash-sdk-bridge",
3023
+ )
3024
+ for key in (
3025
+ "pool_name",
3026
+ "market_symbol",
3027
+ "collateral_symbol",
3028
+ "collateral_amount_raw",
3029
+ "leverage",
3030
+ "side",
3031
+ "estimated_size_usd",
3032
+ "estimated_size_amount_raw",
3033
+ "estimated_collateral_usd",
3034
+ "estimated_collateral_amount_raw",
3035
+ "estimated_entry_price",
3036
+ "estimated_liquidation_price",
3037
+ "estimated_entry_fee_usd",
3038
+ "estimated_total_fee_usd",
3039
+ "position_size_usd",
3040
+ "position_size_amount_raw",
3041
+ "close_amount_raw",
3042
+ "estimated_receive_amount_usd",
3043
+ "estimated_mark_price",
3044
+ "estimated_existing_liquidation_price",
3045
+ "estimated_new_liquidation_price",
3046
+ "estimated_profit_usd",
3047
+ "estimated_loss_usd",
3048
+ "estimated_settled_pnl_usd",
3049
+ "estimated_exit_fee_usd",
3050
+ "estimated_total_fees_usd",
3051
+ "position_pubkey",
3052
+ "verification",
3053
+ ):
3054
+ if key in prepared:
3055
+ result[key] = prepared[key]
3056
+ if "build_response" in prepared:
3057
+ result["build_response"] = prepared["build_response"]
3058
+ return result
3059
+
3060
+ async def execute_flash_trade_open_position(
3061
+ self,
3062
+ *,
3063
+ pool_name: str,
3064
+ market_symbol: str,
3065
+ collateral_symbol: str,
3066
+ collateral_amount_raw: str,
3067
+ leverage: str,
3068
+ side: str,
3069
+ approved_preview: dict[str, Any] | None = None,
3070
+ ) -> dict[str, Any]:
3071
+ preview = (
3072
+ dict(approved_preview)
3073
+ if isinstance(approved_preview, dict)
3074
+ else await self.preview_flash_trade_open_position(
3075
+ pool_name=pool_name,
3076
+ market_symbol=market_symbol,
3077
+ collateral_symbol=collateral_symbol,
3078
+ collateral_amount_raw=collateral_amount_raw,
3079
+ leverage=leverage,
3080
+ side=side,
3081
+ )
3082
+ )
3083
+ prepared = await self._prepare_flash_trade_open_position_from_preview(preview)
3084
+ return await self._execute_prepared_flash_trade_transaction(prepared)
3085
+
3086
+ async def execute_flash_trade_close_position(
3087
+ self,
3088
+ *,
3089
+ pool_name: str,
3090
+ market_symbol: str,
3091
+ side: str,
3092
+ approved_preview: dict[str, Any] | None = None,
3093
+ ) -> dict[str, Any]:
3094
+ preview = (
3095
+ dict(approved_preview)
3096
+ if isinstance(approved_preview, dict)
3097
+ else await self.preview_flash_trade_close_position(
3098
+ pool_name=pool_name,
3099
+ market_symbol=market_symbol,
3100
+ side=side,
3101
+ )
3102
+ )
3103
+ prepared = await self._prepare_flash_trade_close_position_from_preview(preview)
3104
+ return await self._execute_prepared_flash_trade_transaction(prepared)
3105
+
2484
3106
  async def get_kamino_lend_markets(self) -> dict[str, Any]:
2485
3107
  self._require_mainnet_kamino("Kamino lending")
2486
3108
  data = await kamino.fetch_lend_markets()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openclaw-agent-wallet"
7
- version = "0.1.15"
7
+ version = "0.1.17"
8
8
  description = "Plugin-friendly wallet backend for OpenClaw agents"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [