Created
March 25, 2025 21:13
-
-
Save hydrogenbond007/9128cd74b1380e51e1980b5e8091af51 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import streamlit as st | |
| import pandas as pd | |
| import networkx as nx | |
| import plotly.graph_objects as go | |
| from dataclasses import dataclass | |
| from typing import Dict, List, Optional, Set, Tuple | |
| from decimal import Decimal | |
| import asyncio | |
| from datetime import datetime | |
| from tycho import TychoClient, TychoConfig | |
| # Data Models | |
| @dataclass | |
| class Token: | |
| address: str | |
| symbol: str | |
| decimals: int | |
| @dataclass | |
| class Protocol: | |
| name: str | |
| address: str | |
| @dataclass | |
| class Pool: | |
| token1: Token | |
| token2: Token | |
| tvl_usdc: Decimal | |
| protocol: Protocol | |
| last_block: int | |
| last_update: datetime | |
| class PoolExplorer: | |
| def __init__(self): | |
| self.tycho = TychoClient( | |
| TychoConfig( | |
| endpoint="http://localhost:50051", # Default Tycho gRPC endpoint | |
| use_ssl=False | |
| ) | |
| ) | |
| self.pools: List[Pool] = [] | |
| self.tokens: Dict[str, Token] = {} | |
| self.protocols: Dict[str, Protocol] = {} | |
| async def fetch_pools(self): | |
| """Fetch all pools from Tycho indexer""" | |
| # This is a placeholder - implement actual Tycho client calls | |
| pools_data = await self.tycho.get_pools() | |
| for pool_data in pools_data: | |
| token1 = Token( | |
| address=pool_data.token0.address, | |
| symbol=pool_data.token0.symbol, | |
| decimals=pool_data.token0.decimals | |
| ) | |
| token2 = Token( | |
| address=pool_data.token1.address, | |
| symbol=pool_data.token1.symbol, | |
| decimals=pool_data.token1.decimals | |
| ) | |
| protocol = Protocol( | |
| name=pool_data.protocol.name, | |
| address=pool_data.protocol.address | |
| ) | |
| pool = Pool( | |
| token1=token1, | |
| token2=token2, | |
| tvl_usdc=pool_data.tvl_usdc, | |
| protocol=protocol, | |
| last_block=pool_data.last_block, | |
| last_update=pool_data.last_update | |
| ) | |
| self.pools.append(pool) | |
| self.tokens[token1.address] = token1 | |
| self.tokens[token2.address] = token2 | |
| self.protocols[protocol.address] = protocol | |
| def get_filtered_pools( | |
| self, | |
| token_filter: Optional[Set[str]] = None, | |
| protocol_filter: Optional[Set[str]] = None, | |
| min_tvl: Optional[Decimal] = None, | |
| max_tvl: Optional[Decimal] = None | |
| ) -> List[Pool]: | |
| """Filter pools based on criteria""" | |
| filtered = self.pools | |
| if token_filter: | |
| filtered = [ | |
| p for p in filtered | |
| if p.token1.address in token_filter or p.token2.address in token_filter | |
| ] | |
| if protocol_filter: | |
| filtered = [ | |
| p for p in filtered | |
| if p.protocol.address in protocol_filter | |
| ] | |
| if min_tvl is not None: | |
| filtered = [p for p in filtered if p.tvl_usdc >= min_tvl] | |
| if max_tvl is not None: | |
| filtered = [p for p in filtered if p.tvl_usdc <= max_tvl] | |
| return filtered | |
| def create_graph(self, pools: List[Pool]) -> nx.Graph: | |
| """Create a network graph from pools""" | |
| G = nx.Graph() | |
| for pool in pools: | |
| G.add_edge( | |
| pool.token1.address, | |
| pool.token2.address, | |
| weight=float(pool.tvl_usdc), | |
| pool=pool | |
| ) | |
| return G | |
| def find_paths( | |
| self, | |
| graph: nx.Graph, | |
| source: str, | |
| target: str, | |
| min_length: int = 2, | |
| max_length: int = 4 | |
| ) -> List[List[str]]: | |
| """Find all paths between source and target tokens""" | |
| paths = [] | |
| for length in range(min_length, max_length + 1): | |
| paths.extend(list(nx.all_simple_paths(graph, source, target, cutoff=length))) | |
| return paths | |
| def main(): | |
| st.title("Pool Explorer") | |
| st.sidebar.title("Filters") | |
| # Initialize explorer | |
| explorer = PoolExplorer() | |
| # Fetch initial data | |
| if 'initialized' not in st.session_state: | |
| asyncio.run(explorer.fetch_pools()) | |
| st.session_state.initialized = True | |
| # Sidebar filters | |
| token_filter = st.sidebar.multiselect( | |
| "Filter by Tokens", | |
| options=[t.symbol for t in explorer.tokens.values()], | |
| format_func=lambda x: x | |
| ) | |
| protocol_filter = st.sidebar.multiselect( | |
| "Filter by Protocols", | |
| options=[p.name for p in explorer.protocols.values()], | |
| format_func=lambda x: x | |
| ) | |
| min_tvl = st.sidebar.number_input("Min TVL (USDC)", value=0.0) | |
| max_tvl = st.sidebar.number_input("Max TVL (USDC)", value=float('inf')) | |
| # Apply filters | |
| filtered_pools = explorer.get_filtered_pools( | |
| token_filter=set(token_filter) if token_filter else None, | |
| protocol_filter=set(protocol_filter) if protocol_filter else None, | |
| min_tvl=Decimal(str(min_tvl)), | |
| max_tvl=Decimal(str(max_tvl)) if max_tvl != float('inf') else None | |
| ) | |
| # Display metrics | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("Total Pools", len(filtered_pools)) | |
| with col2: | |
| st.metric("Total Protocols", len(set(p.protocol.address for p in filtered_pools))) | |
| with col3: | |
| total_tvl = sum(p.tvl_usdc for p in filtered_pools) | |
| st.metric("Total TVL (USDC)", f"${total_tvl:,.2f}") | |
| # View selector | |
| view = st.radio("View", ["List", "Graph", "Path Finder"]) | |
| if view == "List": | |
| # Display pool list | |
| df = pd.DataFrame([ | |
| { | |
| "Token 1": p.token1.symbol, | |
| "Token 2": p.token2.symbol, | |
| "TVL (USDC)": float(p.tvl_usdc), | |
| "Protocol": p.protocol.name, | |
| "Last Block": p.last_block, | |
| "Last Update": p.last_update | |
| } | |
| for p in filtered_pools | |
| ]) | |
| st.dataframe(df) | |
| elif view == "Graph": | |
| # Create and display network graph | |
| G = explorer.create_graph(filtered_pools) | |
| pos = nx.spring_layout(G) | |
| edge_trace = go.Scatter( | |
| x=[], y=[], | |
| line=dict(width=0.5, color='#888'), | |
| hoverinfo='none', | |
| mode='lines') | |
| node_trace = go.Scatter( | |
| x=[], y=[], | |
| mode='markers', | |
| hoverinfo='text', | |
| marker=dict( | |
| showscale=True, | |
| colorscale='YlGnBu', | |
| size=10, | |
| )) | |
| for edge in G.edges(): | |
| x0, y0 = pos[edge[0]] | |
| x1, y1 = pos[edge[1]] | |
| edge_trace['x'] += (x0, x1, None) | |
| edge_trace['y'] += (y0, y1, None) | |
| node_tvls = [] | |
| node_text = [] | |
| for node in G.nodes(): | |
| x, y = pos[node] | |
| node_trace['x'] += (x,) | |
| node_trace['y'] += (y,) | |
| token = explorer.tokens[node] | |
| tvl = sum( | |
| float(p.tvl_usdc) | |
| for p in filtered_pools | |
| if p.token1.address == node or p.token2.address == node | |
| ) | |
| node_tvls.append(tvl) | |
| node_text.append(f"{token.symbol}<br>TVL: ${tvl:,.2f}") | |
| node_trace.marker.color = node_tvls | |
| node_trace.text = node_text | |
| fig = go.Figure(data=[edge_trace, node_trace], | |
| layout=go.Layout( | |
| showlegend=False, | |
| hovermode='closest', | |
| margin=dict(b=0,l=0,r=0,t=0), | |
| )) | |
| st.plotly_chart(fig) | |
| else: # Path Finder | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| source_token = st.selectbox( | |
| "From Token", | |
| options=[t.symbol for t in explorer.tokens.values()], | |
| key="source_token" | |
| ) | |
| with col2: | |
| target_token = st.selectbox( | |
| "To Token", | |
| options=[t.symbol for t in explorer.tokens.values()], | |
| key="target_token" | |
| ) | |
| if source_token and target_token: | |
| G = explorer.create_graph(filtered_pools) | |
| source_address = next( | |
| t.address for t in explorer.tokens.values() | |
| if t.symbol == source_token | |
| ) | |
| target_address = next( | |
| t.address for t in explorer.tokens.values() | |
| if t.symbol == target_token | |
| ) | |
| paths = explorer.find_paths(G, source_address, target_address) | |
| if paths: | |
| st.write(f"Found {len(paths)} possible paths:") | |
| for path in paths: | |
| path_tokens = [explorer.tokens[addr].symbol for addr in path] | |
| st.write(" -> ".join(path_tokens)) | |
| else: | |
| st.write("No paths found between these tokens.") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment