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.
434 lines
14 KiB
434 lines
14 KiB
1 month ago
|
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}")
|