# -*- coding: utf-8 -*- import asyncio from pymongo import MongoClient import aiohttp import pandas as pd import requests import json import urllib.parse import gradio as gr import datetime import os import subprocess, json def get_lp_positions(node_path="meteora/index.js"): """ Runs the Node.js script, parses the output, and returns a combined DataFrame with tokenX and tokenY positions. """ try: # Run Node.js script result = subprocess.run( ["node", node_path], capture_output=True, text=True ) # Optional: print warnings if result.stderr.strip(): print("Node STDERR:", result.stderr) # Handle Node.js errors if result.returncode != 0: print("Node.js exited with an error") return pd.DataFrame() # return empty DataFrame on failure # Parse JSON output positions = json.loads(result.stdout) if result.stdout.strip() else [] if not positions: return pd.DataFrame() # Normalize JSON to DataFrame lp_df_real = pd.json_normalize(positions) lp_df_real.drop(columns=['positionAccountAddress'], inplace=True, errors='ignore') # Split tokenY y_df_real = lp_df_real[['tokenYAddress','tokenYAmount']].copy() lp_df_real.rename(columns={'tokenXAddress':'symbol','tokenXAmount':'balance'}, inplace=True) y_df_real.rename(columns={'tokenYAddress':'symbol','tokenYAmount':'balance'}, inplace=True) # Combine tokenX and tokenY DataFrames final_lp_df = pd.concat([lp_df_real, y_df_real], ignore_index=True).dropna(axis=1) return final_lp_df except Exception as e: print(f"Error in get_lp_positions: {e}") return pd.DataFrame() # fallback empty DataFrame wallets = [ "41KL7hbeA2dDuX6gCUFhVaozACnx2hLvGBuY4tgrpJ31", "5Xihd2FdAXZnER7HWjT8GiZm14hyPiUpGfqLLL86yaa9", "fYYLvdb82NkUQ6EgT7U6N52A2cN6V1dBKEJuvDwwjKj" ] def parse_solscan_results(results): if isinstance(results, dict): results = [results] records = [] for entry in results: data = entry.get("data", {}) if not data or not data.get("success"): continue payload = data.get("data", {}) # Native SOL native = payload.get("native_balance", {}) if native: records.append({ "symbol": native.get("token_symbol", "SOL"), "balance": native.get("balance", 0), "value": native.get("value", 0) }) # Tokens for token in payload.get("tokens", []): records.append({ "symbol": token.get("token_symbol"), "balance": token.get("balance", 0), "value": token.get("value", 0) }) df = pd.DataFrame(records, columns=["address","symbol", "balance", "value"]) if not df.empty: df = df.groupby(["symbol"], as_index=False).agg({ "balance": "sum", "value": "sum" }) return df def parse_solscan_results_addresses(results): if isinstance(results, dict): results = [results] records = [] for entry in results: address = entry.get("address") data = entry.get("data", {}) if not data or not data.get("success"): continue payload = data.get("data", {}) # Native SOL native = payload.get("native_balance", {}) if native: records.append({ "address": address, "symbol": native.get("token_symbol", "SOL"), "balance": native.get("balance", 0), "value": native.get("value", 0) }) # Tokens for token in payload.get("tokens", []): records.append({ "address": address, "symbol": token.get("token_symbol"), "balance": token.get("balance", 0), "value": token.get("value", 0) }) # Make sure DataFrame has the expected columns even if empty df = pd.DataFrame(records, columns=["address", "symbol", "balance", "value"]) # Only group if DataFrame is not empty if not df.empty: df = df.groupby(["address", "symbol"], as_index=False).agg({ "balance": "sum", "value": "sum" }) return df async def get_sol_balances(sol_addresses): SOLSCAN_KEY = os.getenv('SOLSCAN_KEY') headers = { "token": SOLSCAN_KEY } async def fetch_wallet(session, wallet_address): url = f"https://pro-api.solscan.io/v2.0/account/portfolio?address={wallet_address}&exclude_low_score_tokens=false" async with session.get(url, headers=headers) as response: if response.status != 200: return {"address": wallet_address, "error": f"HTTP {response.status}"} try: data = await response.json() except Exception as e: return {"address": wallet_address, "error": str(e)} res = {"address": wallet_address, "data": data} return parse_solscan_results_addresses([res]) async with aiohttp.ClientSession() as session: tasks = [fetch_wallet(session, addr) for addr in sol_addresses] results = await asyncio.gather(*tasks) final_df = pd.concat(results, ignore_index=True) final_df = final_df.groupby(["address","symbol"], as_index=False).agg({ "balance": "sum", "value": "sum" }) final_df = final_df[final_df['symbol'].isin(['SOL', 'WSOL','HYPER'])] return final_df.groupby('symbol').agg({"balance":"sum","value":"sum"}).reset_index() async def get_all_dfs(): starting_df = pd.DataFrame([{"symbol":"HYPER","balance":5000000,"value":68500},{"symbol":"SOL","balance":240,"value":42960}]) final_df = await get_sol_balances(wallets) hyper_price = float(final_df.loc[final_df['symbol']=='HYPER','value'] / final_df.loc[final_df['symbol']=='HYPER','balance']) sol_price = float(final_df.loc[final_df['symbol']=='SOL','value'] / final_df.loc[final_df['symbol']=='SOL','balance']) symbolMap = {'So11111111111111111111111111111111111111112':'SOL','Aq8Gocyvyyi8xk5EYxd6viUfVmVvs9T9R6mZFzZFpump':'HYPER'} priceMap = {'SOL':sol_price,'HYPER':hyper_price} lp_df0 = get_lp_positions() lp_df0['symbol'] = lp_df0['symbol'].map(symbolMap) lp_df0['value'] = lp_df0['balance'] * lp_df0['symbol'].map(priceMap) lp_df0 = lp_df0.groupby('symbol').agg({'balance':'sum','value':'sum'}).reset_index() #lp_df = pd.DataFrame([{"symbol":"HYPER","balance":2_800_000,"value":2_800_000*hyper_price}]) total_balances = pd.concat([final_df,lp_df0]).groupby('symbol').agg({"balance":"sum","value":"sum"}).reset_index() pnl = total_balances['value'].sum() - starting_df['value'].sum() pnl = f"{pnl:,.2f}" return starting_df,lp_df0,final_df,total_balances,pnl with gr.Blocks() as demo: gr.Markdown("## HYPER Balances ") starting = gr.DataFrame(label= "Starting Balances ") lp_real = gr.DataFrame(label="LP Position Balances") final = gr.DataFrame(label = "Wallet Balances") total_balances = gr.DataFrame(label="Total") pnl = gr.Textbox(label="PnL ($) (AUM)") demo.load( fn= get_all_dfs, inputs = [], outputs=[starting,lp_real,final,total_balances,pnl] ) demo.launch(debug=True, share=True)