10
10
from driftpy .drift_user import DriftUser
11
11
from driftpy .user_map .user_map import UserMap # Assuming UserMap is accessible
12
12
from driftpy .types import PerpPosition , UserAccount , is_variant , OraclePriceData # Added OraclePriceData
13
- from driftpy .constants .numeric_constants import PRICE_PRECISION , MARGIN_PRECISION , BASE_PRECISION # Added BASE_PRECISION
13
+ from driftpy .constants .numeric_constants import PRICE_PRECISION , MARGIN_PRECISION , BASE_PRECISION , QUOTE_PRECISION # Added QUOTE_PRECISION
14
14
from driftpy .constants .perp_markets import mainnet_perp_market_configs # Added mainnet_perp_market_configs
15
15
from driftpy .pickle .vat import Vat # Added Vat for type hinting
16
16
from driftpy .math .margin import MarginCategory # Added MarginCategory
24
24
25
25
# Decimals for perp market base asset amount
26
26
PERP_DECIMALS = 9
27
+ # Slot inactivity threshold for considering a user bootable (approx 10 minutes)
28
+ SLOT_INACTIVITY_THRESHOLD = 9000
29
+ # Optional: Leverage threshold for booting (e.g., 25x)
30
+ # BOOT_LEVERAGE_THRESHOLD = 25 # Not strictly implementing this yet, focusing on inactivity
27
31
28
32
class HighLeverageStats (BaseModel ):
29
33
slot : int # Added slot field
@@ -42,38 +46,44 @@ class HighLeveragePositionDetail(BaseModel):
42
46
account_leverage : float # Renamed from leverage
43
47
position_leverage : float # Added position-specific leverage
44
48
49
+ class BootableUserDetails (BaseModel ):
50
+ user_public_key : str
51
+ authority : str
52
+ account_leverage : float
53
+ activity_staleness_slots : int
54
+ last_active_slot : int
55
+ initial_margin_requirement_usd : float
56
+ total_collateral_usd : float
57
+ health_percent : int # User's health percentage
58
+
45
59
@router .get ("/stats" , response_model = HighLeverageStats )
46
60
async def get_high_leverage_stats (request : BackendRequest ):
47
61
"""
48
62
Provides statistics about high leverage usage on the Drift protocol.
49
63
- Total spots: Maximum users allowed in high leverage mode (hardcoded).
50
64
- Opted-in spots: Users currently opted into high leverage mode.
51
65
- Available spots: Spots remaining for users to opt-in.
52
- - Bootable spots: Users opted-in but not actively using high leverage (no significant perp positions) .
66
+ - Bootable spots: Users opted-in, inactive for a defined period, and potentially with low overall leverage .
53
67
"""
54
68
55
- total_spots = 200 # Hardcoded as per user request
69
+ total_spots = 400 # Hardcoded maximum number of spots
56
70
57
71
opted_in_users_count = 0
58
72
bootable_count = 0
59
73
60
- # --- Get Slot ---
61
- # Use getattr for safer access, default to 0 or handle appropriately if slot is critical
62
- slot = getattr (request .state .backend_state , 'last_oracle_slot' , 0 )
63
- if slot == 0 :
64
- logger .warning ("Could not retrieve last_oracle_slot from backend state." )
65
- # Decide how to handle missing slot: raise error, return default, etc.
66
- # For now, it will return 0 as the slot.
67
-
68
- # --- Access UserMap ---
74
+ current_slot = getattr (request .state .backend_state , 'last_oracle_slot' , 0 )
75
+ if current_slot == 0 :
76
+ logger .warning ("Could not retrieve current_slot (last_oracle_slot) from backend state for /stats. Bootable check might be inaccurate." )
77
+ # If current_slot is critical for bootable check, might return error or default.
78
+ # For now, proceeding will mean inactivity check can't be reliably performed if current_slot is 0.
79
+
69
80
user_map : Optional [UserMap ] = getattr (request .state .backend_state , 'user_map' , None )
70
81
logger .info (f"UserMap object type from state: { type (user_map )} " )
71
82
72
83
if not user_map or not hasattr (user_map , 'values' ):
73
84
logger .warning ("UserMap not found or invalid in backend state. Returning default stats." )
74
- # Return default values, including the fetched slot (which might be 0)
75
85
return HighLeverageStats (
76
- slot = slot ,
86
+ slot = current_slot ,
77
87
total_spots = total_spots ,
78
88
available_spots = total_spots ,
79
89
bootable_spots = 0 ,
@@ -86,13 +96,13 @@ async def get_high_leverage_stats(request: BackendRequest):
86
96
87
97
if not user_values :
88
98
logger .info ("UserMap is empty for /stats." )
89
- else :
90
- logger .info (f"First user object type for /stats: { type (user_values [0 ])} " )
99
+ # else: # Logging first user type can be verbose, let's assume it's DriftUser by now
100
+ # logger.info(f"First user object type for /stats: {type(user_values[0])}")
91
101
92
102
except Exception as e :
93
103
logger .error (f"Error getting users from UserMap for /stats: { e } " , exc_info = True )
94
104
return HighLeverageStats (
95
- slot = slot ,
105
+ slot = current_slot ,
96
106
total_spots = total_spots ,
97
107
available_spots = total_spots ,
98
108
bootable_spots = 0 ,
@@ -105,37 +115,47 @@ async def get_high_leverage_stats(request: BackendRequest):
105
115
continue
106
116
107
117
is_high_leverage = False
118
+ user_account : Optional [UserAccount ] = None # Define here for broader scope
108
119
try :
109
120
is_high_leverage = user .is_high_leverage_mode ()
121
+ if is_high_leverage :
122
+ user_account = user .get_user_account ()
110
123
except Exception as e :
111
- logger .error (f"Error checking high leverage status for user { user .user_public_key } in /stats: { e } " , exc_info = True )
124
+ logger .error (f"Error checking high leverage status or getting account for user { user .user_public_key } in /stats: { e } " , exc_info = True )
112
125
continue
113
126
114
- if is_high_leverage :
127
+ if is_high_leverage and user_account :
115
128
opted_in_users_count += 1
116
129
117
- has_open_perp_positions = False
118
- try :
119
- user_account : UserAccount = user .get_user_account ()
120
- perp_positions : list [PerpPosition ] = user_account .perp_positions
121
-
122
- for position in perp_positions :
123
- if position .base_asset_amount != 0 :
124
- has_open_perp_positions = True
125
- break
126
- except Exception as e :
127
- logger .error (f"Error checking positions for user { user .user_public_key } in /stats: { e } " , exc_info = True )
128
- has_open_perp_positions = True
130
+ # Check for bootable status based on inactivity
131
+ is_inactive = False
132
+ if current_slot > 0 : # Ensure current_slot is valid before checking inactivity
133
+ try :
134
+ last_active_slot = user_account .last_active_slot # This is a int/BN
135
+ # Ensure last_active_slot can be converted to int if it's a BN or similar type
136
+ last_active_slot_int = int (str (last_active_slot ))
137
+ if (current_slot - last_active_slot_int ) > SLOT_INACTIVITY_THRESHOLD :
138
+ is_inactive = True
139
+ logger .debug (f"User { user .user_public_key } is inactive. Current: { current_slot } , Last Active: { last_active_slot_int } , Diff: { current_slot - last_active_slot_int } " )
140
+ except Exception as slot_check_e :
141
+ logger .error (f"Error checking inactivity for user { user .user_public_key } : { slot_check_e } " , exc_info = True )
142
+ # Decide behavior: treat as not inactive, or skip bootable check for this user
129
143
130
- if not has_open_perp_positions :
144
+ # The bot script uses inactivity and a general low leverage threshold.
145
+ # For simplicity and alignment with bot, we use inactivity as the primary signal.
146
+ # A stricter check could verify no *significant* positions or overall low leverage.
147
+ if is_inactive :
148
+ # Optionally, add the leverage check here if desired for stricter booting criteria:
149
+ # current_leverage_ui = user.get_leverage() / MARGIN_PRECISION
150
+ # if current_leverage_ui < BOOT_LEVERAGE_THRESHOLD:
151
+ # bootable_count += 1
131
152
bootable_count += 1
132
153
133
154
available_spots = total_spots - opted_in_users_count
134
- logger .info (f"Calculated Stats for /stats: Slot={ slot } , Total={ total_spots } , OptedIn={ opted_in_users_count } , Available={ available_spots } , Bootable={ bootable_count } " )
155
+ logger .info (f"Calculated Stats for /stats: Slot={ current_slot } , Total={ total_spots } , OptedIn={ opted_in_users_count } , Available={ available_spots } , Bootable={ bootable_count } " )
135
156
136
- # Include slot in the returned object
137
157
return HighLeverageStats (
138
- slot = slot ,
158
+ slot = current_slot ,
139
159
total_spots = total_spots ,
140
160
available_spots = available_spots ,
141
161
bootable_spots = bootable_count ,
@@ -252,8 +272,74 @@ async def get_high_leverage_positions_detailed(request: BackendRequest):
252
272
logger .info (f"Returning { len (detailed_hl_positions )} high leverage positions." )
253
273
return detailed_hl_positions
254
274
275
+ @router .get ("/bootable-users" , response_model = List [BootableUserDetails ])
276
+ async def get_bootable_user_details (request : BackendRequest ):
277
+ """
278
+ Returns detailed information for users who are in high leverage mode and deemed bootable due to inactivity.
279
+ """
280
+ bootable_users_list : List [BootableUserDetails ] = []
281
+
282
+ current_slot = getattr (request .state .backend_state , 'last_oracle_slot' , 0 )
283
+ user_map : Optional [UserMap ] = getattr (request .state .backend_state , 'user_map' , None )
284
+
285
+ logger .info (f"Fetching bootable users details. Current slot: { current_slot } " )
286
+
287
+ if current_slot == 0 :
288
+ logger .warning ("Current slot is 0, cannot accurately determine bootable users by inactivity. Returning empty list." )
289
+ return []
290
+
291
+ if not user_map or not hasattr (user_map , 'values' ):
292
+ logger .warning ("UserMap not found or invalid in backend state. Returning empty list for /bootable-users." )
293
+ return []
294
+
295
+ try :
296
+ user_values = list (user_map .values ())
297
+ logger .info (f"Processing { len (user_values )} users from UserMap for /bootable-users." )
298
+ except Exception as e :
299
+ logger .error (f"Error getting users from UserMap for /bootable-users: { e } " , exc_info = True )
300
+ return []
301
+
302
+ for user in user_values :
303
+ if not isinstance (user , DriftUser ):
304
+ logger .warning (f"Skipping item, expected DriftUser, got { type (user )} " )
305
+ continue
306
+
307
+ user_account : Optional [UserAccount ] = None
308
+ try :
309
+ if user .is_high_leverage_mode ():
310
+ user_account = user .get_user_account ()
311
+ if not user_account :
312
+ logger .warning (f"User { user .user_public_key } is high leverage but failed to get user_account. Skipping." )
313
+ continue
314
+
315
+ last_active_slot_int = int (str (user_account .last_active_slot ))
316
+ activity_staleness_slots = current_slot - last_active_slot_int
317
+
318
+ if activity_staleness_slots > SLOT_INACTIVITY_THRESHOLD :
319
+ logger .debug (f"User { user .user_public_key } is bootable. Staleness: { activity_staleness_slots } slots." )
320
+
321
+ account_leverage_ui = user .get_leverage () / MARGIN_PRECISION
322
+ initial_margin_req_usd = user .get_margin_requirement (MarginCategory .INITIAL ) / QUOTE_PRECISION
323
+ total_collateral_usd = user .get_total_collateral (MarginCategory .INITIAL ) / QUOTE_PRECISION
324
+ health_percent = user .get_health ()
325
+ user_public_key_str = str (user .user_public_key )
326
+ authority_str = str (user_account .authority )
255
327
256
- # Example of how to include this router in a main FastAPI app:
257
- # from fastapi import FastAPI
258
- # app = FastAPI()
259
- # app.include_router(router, prefix="/high-leverage", tags=["High Leverage"])
328
+ bootable_users_list .append (
329
+ BootableUserDetails (
330
+ user_public_key = user_public_key_str ,
331
+ authority = authority_str ,
332
+ account_leverage = account_leverage_ui ,
333
+ activity_staleness_slots = activity_staleness_slots ,
334
+ last_active_slot = last_active_slot_int ,
335
+ initial_margin_requirement_usd = initial_margin_req_usd ,
336
+ total_collateral_usd = total_collateral_usd ,
337
+ health_percent = health_percent ,
338
+ )
339
+ )
340
+ except Exception as user_proc_e :
341
+ logger .error (f"Error processing user { getattr (user , 'user_public_key' , 'UNKNOWN' )} for /bootable-users: { user_proc_e } " , exc_info = True )
342
+ continue
343
+
344
+ logger .info (f"Found { len (bootable_users_list )} bootable users." )
345
+ return bootable_users_list
0 commit comments