Skip to content

Instantly share code, notes, and snippets.

@hydrogenbond007
Created March 25, 2025 21:13
Show Gist options
  • Select an option

  • Save hydrogenbond007/9128cd74b1380e51e1980b5e8091af51 to your computer and use it in GitHub Desktop.

Select an option

Save hydrogenbond007/9128cd74b1380e51e1980b5e8091af51 to your computer and use it in GitHub Desktop.
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