Spaces:
Sleeping
Sleeping
| # -*- 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) | |