@agentlayer.tech/wallet 0.1.15 → 0.1.16
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/dist/index.js +117 -1
- package/.openclaw/extensions/agent-wallet/index.ts +117 -1
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +4 -0
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/.openclaw/extensions/pay-bridge/package.json +1 -1
- package/CHANGELOG.md +14 -0
- package/RELEASING.md +97 -0
- package/agent-wallet/.env.example +5 -0
- package/agent-wallet/README.md +24 -0
- package/agent-wallet/agent_wallet/config.py +4 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +504 -0
- package/agent-wallet/agent_wallet/providers/flash.py +186 -0
- package/agent-wallet/agent_wallet/providers/flash_sdk_bridge.py +251 -0
- package/agent-wallet/agent_wallet/transaction_policy.py +79 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +78 -0
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +623 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/flash-sdk-bridge/README.md +33 -0
- package/agent-wallet/scripts/flash-sdk-bridge/bridge.mjs +1179 -0
- package/agent-wallet/scripts/flash-sdk-bridge/package-lock.json +2377 -0
- package/agent-wallet/scripts/flash-sdk-bridge/package.json +12 -0
- package/agent-wallet/scripts/install_agent_wallet.py +46 -11
- package/agent-wallet/scripts/install_openclaw_local_config.py +4 -0
- package/package.json +2 -1
|
@@ -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
|
+
if normalized_collateral_symbol != normalized_market_symbol:
|
|
2604
|
+
raise WalletBackendError(
|
|
2605
|
+
"Phase 2 Flash preview currently supports only same-collateral opens where collateral_symbol matches market_symbol."
|
|
2606
|
+
)
|
|
2607
|
+
normalized_collateral_amount_raw = _require_positive_integer_string(
|
|
2608
|
+
collateral_amount_raw,
|
|
2609
|
+
field_name="collateral_amount_raw",
|
|
2610
|
+
)
|
|
2611
|
+
normalized_leverage = _require_positive_decimal_string(leverage, field_name="leverage")
|
|
2612
|
+
normalized_side = _normalize_flash_side(side)
|
|
2613
|
+
market_snapshot = await self.get_flash_trade_markets(pool_name=normalized_pool_name)
|
|
2614
|
+
matching_market = next(
|
|
2615
|
+
(
|
|
2616
|
+
item
|
|
2617
|
+
for item in market_snapshot["markets"]
|
|
2618
|
+
if isinstance(item, dict)
|
|
2619
|
+
and str(item.get("symbol") or "").strip().upper() == normalized_market_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."
|
|
2626
|
+
)
|
|
2627
|
+
bridge_preview = await flash_sdk_bridge.preview_open_position_same_collateral(
|
|
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_same_collateral(
|
|
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_same_collateral(
|
|
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()
|