Skip to content

Commit 5c642dd

Browse files
authored
Merge pull request #33 from drift-labs:goldhaxx/DPE-3175/build-dashboard-for-high-leverage-user-spots-and-status
Refactor High Leverage API and Streamlit Page for Enhanced Data Handling
2 parents 5a6053b + 0c90922 commit 5c642dd

File tree

2 files changed

+110
-90
lines changed

2 files changed

+110
-90
lines changed

backend/api/high_leverage_api.py

Lines changed: 90 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from backend.state import BackendRequest
88

99
# Drift imports - adjust paths/names if necessary based on project structure
10+
from driftpy.drift_client import DriftClient # For interacting with the Drift program
11+
from driftpy.addresses import get_high_leverage_mode_config_public_key # To get the PDA for config
1012
from driftpy.drift_user import DriftUser
1113
from driftpy.user_map.user_map import UserMap # Assuming UserMap is accessible
1214
from driftpy.types import PerpPosition, UserAccount, is_variant, OraclePriceData # Added OraclePriceData
@@ -60,106 +62,110 @@ class BootableUserDetails(BaseModel):
6062
async def get_high_leverage_stats(request: BackendRequest):
6163
"""
6264
Provides statistics about high leverage usage on the Drift protocol.
63-
- Total spots: Maximum users allowed in high leverage mode (hardcoded).
65+
- Total spots: Maximum users allowed in high leverage mode.
6466
- Opted-in spots: Users currently opted into high leverage mode.
6567
- Available spots: Spots remaining for users to opt-in.
66-
- Bootable spots: Users opted-in, inactive for a defined period, and potentially with low overall leverage.
68+
- Bootable spots: Users opted-in, inactive for a defined period.
6769
"""
6870

69-
total_spots = 400 # Hardcoded maximum number of spots
70-
71-
opted_in_users_count = 0
71+
# Initialize with fallback values
72+
total_spots = 100 # Fallback, will be updated from on-chain if possible
73+
opted_in_users_count = 0 # Fallback, will be updated from on-chain if possible
7274
bootable_count = 0
7375

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-
80-
user_map: Optional[UserMap] = getattr(request.state.backend_state, 'user_map', None)
81-
logger.info(f"UserMap object type from state: {type(user_map)}")
82-
83-
if not user_map or not hasattr(user_map, 'values'):
84-
logger.warning("UserMap not found or invalid in backend state. Returning default stats.")
85-
return HighLeverageStats(
86-
slot=current_slot,
87-
total_spots=total_spots,
88-
available_spots=total_spots,
89-
bootable_spots=0,
90-
opted_in_spots=0
91-
)
92-
93-
try:
94-
user_values = list(user_map.values())
95-
logger.info(f"Processing {len(user_values)} users from UserMap for /stats.")
96-
97-
if not user_values:
98-
logger.info("UserMap is empty for /stats.")
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])}")
101-
102-
except Exception as e:
103-
logger.error(f"Error getting users from UserMap for /stats: {e}", exc_info=True)
104-
return HighLeverageStats(
105-
slot=current_slot,
106-
total_spots=total_spots,
107-
available_spots=total_spots,
108-
bootable_spots=0,
109-
opted_in_spots=0
110-
)
76+
current_slot = getattr(request.state.backend_state, 'last_oracle_slot', 0)
77+
config_account = None # To track if on-chain data was fetched
11178

112-
for user in user_values:
113-
if not isinstance(user, DriftUser):
114-
logger.warning(f"Skipping item in user_map values for /stats, expected DriftUser, got {type(user)}")
115-
continue
79+
# Fetching On-Chain Data for total_spots and opted_in_users_count
80+
drift_client: Optional[DriftClient] = getattr(request.state.backend_state, 'dc', None)
11681

117-
is_high_leverage = False
118-
user_account: Optional[UserAccount] = None # Define here for broader scope
82+
if drift_client:
11983
try:
120-
is_high_leverage = user.is_high_leverage_mode()
121-
if is_high_leverage:
122-
user_account = user.get_user_account()
84+
high_leverage_mode_config_pda = get_high_leverage_mode_config_public_key(
85+
drift_client.program_id
86+
)
87+
config_account = await drift_client.program.account["HighLeverageModeConfig"].fetch(
88+
high_leverage_mode_config_pda
89+
)
90+
total_spots = config_account.max_users
91+
opted_in_users_count = config_account.current_users
92+
logger.info(f"Successfully fetched HighLeverageModeConfig: max_users={total_spots}, current_users={opted_in_users_count}")
12393
except Exception as e:
124-
logger.error(f"Error checking high leverage status or getting account for user {user.user_public_key} in /stats: {e}", exc_info=True)
125-
continue
94+
logger.error(f"Error fetching HighLeverageModeConfig from on-chain: {e}. Using fallback values for total and opted-in spots.", exc_info=True)
95+
# Fallback values initialized earlier will be used.
96+
else:
97+
logger.warning("DriftClient (as 'dc') not found in backend state. Using fallback values for total and opted-in spots.")
98+
# Fallback values initialized earlier will be used.
12699

127-
if is_high_leverage and user_account:
128-
opted_in_users_count += 1
129-
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
143-
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
152-
bootable_count += 1
100+
# Calculating bootable_count (UserMap Iteration)
101+
user_map: Optional[UserMap] = getattr(request.state.backend_state, 'user_map', None)
153102

103+
if not user_map or not hasattr(user_map, 'values'):
104+
logger.warning("UserMap not found or invalid in backend state. Bootable spots count will be 0.")
105+
# opted_in_users_count is already set (from on-chain or fallback)
106+
# bootable_count remains 0
107+
else:
108+
if current_slot == 0:
109+
logger.warning("Could not retrieve current_slot (last_oracle_slot) from backend state for bootable check. Bootable count might be inaccurate (0).")
110+
# bootable_count remains 0 if current_slot is unavailable for inactivity check
111+
else:
112+
try:
113+
user_values = list(user_map.values())
114+
logger.info(f"Processing {len(user_values)} users from UserMap for bootable status calculation.")
115+
116+
for user in user_values:
117+
if not isinstance(user, DriftUser):
118+
logger.warning(f"Skipping item in user_map values for bootable calc, expected DriftUser, got {type(user)}")
119+
continue
120+
121+
is_high_leverage = False
122+
user_account: Optional[UserAccount] = None
123+
try:
124+
is_high_leverage = user.is_high_leverage_mode()
125+
if is_high_leverage: # Only proceed if user is in high leverage mode
126+
user_account = user.get_user_account()
127+
except Exception as e:
128+
logger.error(f"Error checking high leverage status or getting account for user {user.user_public_key} in bootable calc: {e}", exc_info=True)
129+
continue
130+
131+
if is_high_leverage and user_account:
132+
# DO NOT increment opted_in_users_count here.
133+
134+
is_inactive = False
135+
# current_slot validity is already checked before this loop
136+
try:
137+
last_active_slot = user_account.last_active_slot
138+
last_active_slot_int = int(str(last_active_slot))
139+
if (current_slot - last_active_slot_int) > SLOT_INACTIVITY_THRESHOLD:
140+
is_inactive = True
141+
logger.debug(f"User {user.user_public_key} is inactive for bootable check. Current: {current_slot}, Last Active: {last_active_slot_int}, Diff: {current_slot - last_active_slot_int}")
142+
except Exception as slot_check_e:
143+
logger.error(f"Error checking inactivity for user {user.user_public_key} (bootable calc): {slot_check_e}", exc_info=True)
144+
145+
if is_inactive:
146+
bootable_count += 1
147+
except Exception as e:
148+
logger.error(f"Error getting or processing users from UserMap for bootable calculation: {e}", exc_info=True)
149+
# bootable_count remains as it was before the try block or 0 if it's the first error.
150+
151+
# Calculating available_spots
154152
available_spots = total_spots - opted_in_users_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}")
153+
available_spots = max(0, available_spots) # Ensure not negative
154+
155+
logger.info(
156+
f"Calculated Stats for /stats: Slot={current_slot}, "
157+
f"Total={total_spots} (Source: {'On-chain' if config_account else 'Fallback'}), "
158+
f"OptedIn={opted_in_users_count} (Source: {'On-chain' if config_account else 'Fallback'}), "
159+
f"Available={available_spots}, "
160+
f"Bootable={bootable_count} (Source: UserMap)"
161+
)
156162

157163
return HighLeverageStats(
158164
slot=current_slot,
159165
total_spots=total_spots,
160166
available_spots=available_spots,
161167
bootable_spots=bootable_count,
162-
opted_in_spots=opted_in_users_count
168+
opted_in_spots=opted_in_users_count # This matches the model field name
163169
)
164170

165171
@router.get("/positions/detailed", response_model=List[HighLeveragePositionDetail])
@@ -194,6 +200,9 @@ async def get_high_leverage_positions_detailed(request: BackendRequest):
194200

195201
try:
196202
if user.is_high_leverage_mode():
203+
high_leverage_count = getattr(request.state.backend_state, 'high_leverage_count', 0) + 1
204+
setattr(request.state.backend_state, 'high_leverage_count', high_leverage_count)
205+
logger.info(f"Found high leverage mode user: {user.user_public_key} (Total: {high_leverage_count})")
197206
user_account: UserAccount = user.get_user_account()
198207
user_public_key_str = str(user.user_public_key)
199208
authority_str = str(user_account.authority)

src/page/high_leverage_page.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ def high_leverage_page():
6666
# positions_data = result_positions.get("data", []) if isinstance(result_positions, dict) else []
6767

6868
# --- 5. Display Slot Info ---
69-
current_slot = get_current_slot()
70-
if slot != "N/A" and current_slot:
71-
try:
72-
slot_age = int(current_slot) - int(slot)
73-
st.info(f"Displaying data for slot {slot} (age: {slot_age} slots)")
74-
except (ValueError, TypeError):
75-
st.info(f"Displaying data for slot {slot}. Current slot: {current_slot}")
76-
else:
77-
st.info(f"Slot information unavailable. Current slot: {current_slot}")
69+
# current_slot = get_current_slot()
70+
# if slot != "N/A" and current_slot:
71+
# try:
72+
# slot_age = int(current_slot) - int(slot)
73+
# st.info(f"Displaying data for slot {slot} (age: {slot_age} slots)")
74+
# except (ValueError, TypeError):
75+
# st.info(f"Displaying data for slot {slot}. Current slot: {current_slot}")
76+
# else:
77+
# st.info(f"Slot information unavailable. Current slot: {current_slot}")
7878

7979
# --- 6. Prepare Positions DataFrame ---
8080
df_positions = pd.DataFrame()
@@ -132,6 +132,17 @@ def high_leverage_page():
132132
cols[2].metric("Available Spots", stats_data.get('available_spots', 'N/A'))
133133
cols[3].metric("Bootable Spots (Inactive)", stats_data.get('bootable_spots', 'N/A')) # Updated label
134134

135+
# --- Slot Info (Moved) ---
136+
current_slot = get_current_slot()
137+
if slot != "N/A" and current_slot:
138+
try:
139+
slot_age = int(current_slot) - int(slot)
140+
st.info(f"Below data for slot {slot} (age: {slot_age} slots)")
141+
except (ValueError, TypeError):
142+
st.info(f"Below data for slot {slot}. Current slot: {current_slot}")
143+
else:
144+
st.info(f"Slot information unavailable. Current slot: {current_slot}")
145+
135146
st.subheader("Detailed High Leverage Positions")
136147
if not display_df.empty:
137148
# Apply Pandas Styler for formatting

0 commit comments

Comments
 (0)