You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
swarms/sol_agent.py

434 lines
14 KiB

import asyncio
import json
from dataclasses import asdict, dataclass
from datetime import datetime
from typing import Dict, List, Optional, Set
import aiohttp
import matplotlib.pyplot as plt
import networkx as nx
import websockets
from loguru import logger
from swarms import Agent
TREND_AGENT_PROMPT = """You are a specialized blockchain trend analysis agent. Your role:
1. Analyze transaction patterns in Solana blockchain data
2. Identify volume trends, price movements, and temporal patterns
3. Focus on whale movements and their market impact
4. Format findings in clear, structured JSON
5. Include confidence scores for each insight
6. Flag unusual patterns or anomalies
7. Provide historical context for significant movements
Output format:
{
"trends": [
{"pattern": str, "confidence": float, "impact": str}
],
"whale_activity": {...},
"temporal_analysis": {...}
}"""
RISK_AGENT_PROMPT = """You are a blockchain risk assessment specialist. Your tasks:
1. Identify suspicious transaction patterns
2. Monitor for known exploit signatures
3. Assess wallet clustering and relationship patterns
4. Evaluate transaction velocity and size anomalies
5. Check for bridge-related risks
6. Monitor smart contract interactions
7. Flag potential wash trading
Output format:
{
"risk_score": float,
"flags": [...],
"recommendations": [...]
}"""
SUMMARY_AGENT_PROMPT = """You are a blockchain data synthesis expert. Your responsibilities:
1. Combine insights from trend and risk analyses
2. Prioritize actionable intelligence
3. Highlight critical patterns
4. Generate executive summaries
5. Provide market context
6. Make predictions with confidence intervals
7. Suggest trading strategies based on data
Output format:
{
"key_insights": [...],
"market_impact": str,
"recommendations": {...}
}"""
@dataclass
class Transaction:
signature: str
timestamp: datetime
amount: float
from_address: str
to_address: str
class SolanaRPC:
def __init__(
self, endpoint="https://api.mainnet-beta.solana.com"
):
self.endpoint = endpoint
self.session = None
async def get_signatures(self, address: str) -> List[Dict]:
if not self.session:
self.session = aiohttp.ClientSession()
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "getSignaturesForAddress",
"params": [address, {"limit": 100}],
}
async with self.session.post(
self.endpoint, json=payload
) as response:
result = await response.json()
return result.get("result", [])
async def get_transaction(self, signature: str) -> Dict:
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "getTransaction",
"params": [
signature,
{
"encoding": "json",
"maxSupportedTransactionVersion": 0,
},
],
}
async with self.session.post(
self.endpoint, json=payload
) as response:
result = await response.json()
return result.get("result", {})
class AlertSystem:
def __init__(self, email: str, threshold: float = 1000.0):
self.email = email
self.threshold = threshold
self.smtp_server = "smtp.gmail.com"
self.smtp_port = 587
async def check_and_alert(
self, transaction: Transaction, risk_score: float
):
if transaction.amount > self.threshold or risk_score > 0.8:
await self.send_alert(transaction, risk_score)
async def send_alert(
self, transaction: Transaction, risk_score: float
):
# msg = MIMEText(
# f"High-risk transaction detected:\n"
# f"Amount: {transaction.amount} SOL\n"
# f"Risk Score: {risk_score}\n"
# f"Signature: {transaction.signature}"
# )
logger.info(
f"Alert sent for transaction {transaction.signature}"
)
class WalletClusterAnalyzer:
def __init__(self):
self.graph = nx.Graph()
self.known_wallets: Set[str] = set()
def update_graph(self, transaction: Transaction):
self.graph.add_edge(
transaction.from_address,
transaction.to_address,
weight=transaction.amount,
)
self.known_wallets.add(transaction.from_address)
self.known_wallets.add(transaction.to_address)
def identify_clusters(self) -> Dict:
communities = nx.community.greedy_modularity_communities(
self.graph
)
return {
"clusters": [list(c) for c in communities],
"central_wallets": [
wallet
for wallet in self.known_wallets
if self.graph.degree[wallet] > 5
],
}
class TransactionVisualizer:
def __init__(self):
self.transaction_history = []
def add_transaction(self, transaction: Transaction):
self.transaction_history.append(asdict(transaction))
def generate_volume_chart(self) -> str:
volumes = [tx["amount"] for tx in self.transaction_history]
plt.figure(figsize=(12, 6))
plt.plot(volumes)
plt.title("Transaction Volume Over Time")
plt.savefig("volume_chart.png")
return "volume_chart.png"
def generate_network_graph(
self, wallet_analyzer: WalletClusterAnalyzer
) -> str:
plt.figure(figsize=(15, 15))
pos = nx.spring_layout(wallet_analyzer.graph)
nx.draw(
wallet_analyzer.graph,
pos,
node_size=1000,
node_color="lightblue",
with_labels=True,
)
plt.savefig("network_graph.png")
return "network_graph.png"
class SolanaMultiAgentAnalyzer:
def __init__(
self,
min_amount: float = 50.0,
websocket_url: str = "wss://api.mainnet-beta.solana.com",
alert_email: str = None,
):
self.rpc = SolanaRPC()
self.websocket_url = websocket_url
self.min_amount = min_amount
self.transactions = []
self.wallet_analyzer = WalletClusterAnalyzer()
self.visualizer = TransactionVisualizer()
self.alert_system = (
AlertSystem(alert_email) if alert_email else None
)
self.trend_agent = Agent(
agent_name="trend-analyzer",
system_prompt=TREND_AGENT_PROMPT,
model_name="gpt-4o-mini",
max_loops=1,
streaming_on=True,
)
self.risk_agent = Agent(
agent_name="risk-analyzer",
system_prompt=RISK_AGENT_PROMPT,
model_name="gpt-4o-mini",
max_loops=1,
streaming_on=True,
)
self.summary_agent = Agent(
agent_name="summary-agent",
system_prompt=SUMMARY_AGENT_PROMPT,
model_name="gpt-4o-mini",
max_loops=1,
streaming_on=True,
)
logger.add(
"solana_analysis.log", rotation="500 MB", level="INFO"
)
async def start_websocket_stream(self):
async with websockets.connect(
self.websocket_url
) as websocket:
subscribe_message = {
"jsonrpc": "2.0",
"id": 1,
"method": "programSubscribe",
"params": [
"11111111111111111111111111111111",
{"encoding": "json", "commitment": "confirmed"},
],
}
await websocket.send(json.dumps(subscribe_message))
while True:
try:
msg = await websocket.recv()
transaction = await self.parse_websocket_message(
msg
)
if (
transaction
and transaction.amount >= self.min_amount
):
await self.process_transaction(transaction)
except Exception as e:
logger.error(f"Websocket error: {e}")
await asyncio.sleep(5)
async def parse_websocket_message(
self, msg: str
) -> Optional[Transaction]:
try:
data = json.loads(msg)
if "params" in data and "result" in data["params"]:
tx_data = data["params"]["result"]
return Transaction(
signature=tx_data["signature"],
timestamp=datetime.fromtimestamp(
tx_data["blockTime"]
),
amount=float(
tx_data["meta"]["postBalances"][0]
- tx_data["meta"]["preBalances"][0]
)
/ 1e9,
from_address=tx_data["transaction"]["message"][
"accountKeys"
][0],
to_address=tx_data["transaction"]["message"][
"accountKeys"
][1],
)
except Exception as e:
logger.error(f"Error parsing websocket message: {e}")
return None
async def process_transaction(self, transaction: Transaction):
self.wallet_analyzer.update_graph(transaction)
self.visualizer.add_transaction(transaction)
risk_analysis = await self.risk_agent.run(
f"Analyze risk for transaction: {json.dumps(asdict(transaction))}"
)
if self.alert_system:
await self.alert_system.check_and_alert(
transaction, risk_analysis.get("risk_score", 0)
)
async def fetch_transactions(self) -> List[Transaction]:
try:
signatures = await self.rpc.get_signatures(
"11111111111111111111111111111111"
)
transactions = []
for sig_info in signatures:
tx_data = await self.rpc.get_transaction(
sig_info["signature"]
)
if not tx_data or "meta" not in tx_data:
continue
pre_balances = tx_data["meta"]["preBalances"]
post_balances = tx_data["meta"]["postBalances"]
amount = abs(pre_balances[0] - post_balances[0]) / 1e9
if amount >= self.min_amount:
tx = Transaction(
signature=sig_info["signature"],
timestamp=datetime.fromtimestamp(
tx_data["blockTime"]
),
amount=amount,
from_address=tx_data["transaction"][
"message"
]["accountKeys"][0],
to_address=tx_data["transaction"]["message"][
"accountKeys"
][1],
)
transactions.append(tx)
return transactions
except Exception as e:
logger.error(f"Error fetching transactions: {e}")
return []
async def analyze_transactions(
self, transactions: List[Transaction]
) -> Dict:
tx_data = [asdict(tx) for tx in transactions]
cluster_data = self.wallet_analyzer.identify_clusters()
trend_analysis = await self.trend_agent.run(
f"Analyze trends in: {json.dumps(tx_data)}"
)
print(trend_analysis)
risk_analysis = await self.risk_agent.run(
f"Analyze risks in: {json.dumps({'transactions': tx_data, 'clusters': cluster_data})}"
)
print(risk_analysis)
summary = await self.summary_agent.run(
f"Synthesize insights from: {trend_analysis}, {risk_analysis}"
)
print(summary)
volume_chart = self.visualizer.generate_volume_chart()
network_graph = self.visualizer.generate_network_graph(
self.wallet_analyzer
)
return {
"transactions": tx_data,
"trend_analysis": trend_analysis,
"risk_analysis": risk_analysis,
"cluster_analysis": cluster_data,
"summary": summary,
"visualizations": {
"volume_chart": volume_chart,
"network_graph": network_graph,
},
}
async def run_continuous_analysis(self):
logger.info("Starting continuous analysis")
asyncio.create_task(self.start_websocket_stream())
while True:
try:
transactions = await self.fetch_transactions()
if transactions:
analysis = await self.analyze_transactions(
transactions
)
timestamp = datetime.now().strftime(
"%Y%m%d_%H%M%S"
)
with open(f"analysis_{timestamp}.json", "w") as f:
json.dump(analysis, f, indent=2, default=str)
logger.info(
f"Analysis completed: analysis_{timestamp}.json"
)
await asyncio.sleep(60)
except Exception as e:
logger.error(f"Error in analysis loop: {e}")
await asyncio.sleep(60)
# Add to __main__:
if __name__ == "__main__":
logger.info("Starting Solana analyzer...")
analyzer = SolanaMultiAgentAnalyzer(alert_email="your@email.com")
try:
asyncio.run(analyzer.run_continuous_analysis())
except Exception as e:
logger.error(f"Critical error: {e}")