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/new_features_examples/solana_tool/solana_tool_test.py

303 lines
9.0 KiB

1 week ago
from typing import List
2 weeks ago
from datetime import datetime
import json
import requests
from loguru import logger
from dataclasses import dataclass
1 week ago
from datetime import timezone
2 weeks ago
import time
import random
# Configure loguru logger
logger.add(
"solana_transactions.log",
rotation="500 MB",
retention="10 days",
level="INFO",
1 week ago
format="{time} {level} {message}",
2 weeks ago
)
# Most reliable RPC endpoints
RPC_ENDPOINTS = [
"https://api.mainnet-beta.solana.com",
"https://rpc.ankr.com/solana",
1 week ago
"https://solana.getblock.io/mainnet",
2 weeks ago
]
1 week ago
2 weeks ago
@dataclass
class TransactionError:
"""Data class to represent transaction errors"""
1 week ago
2 weeks ago
error_type: str
message: str
timestamp: str = datetime.now(timezone.utc).isoformat()
1 week ago
2 weeks ago
class SolanaAPIException(Exception):
"""Custom exception for Solana API related errors"""
1 week ago
2 weeks ago
pass
1 week ago
2 weeks ago
class RPCEndpointManager:
"""Manages RPC endpoints and handles switching between them"""
1 week ago
2 weeks ago
def __init__(self, endpoints: List[str]):
self.endpoints = endpoints.copy()
self.current_endpoint = self.endpoints[0]
self.last_request_time = 0
self.min_request_interval = 0.2 # Increased minimum interval
self.total_requests = 0
self.max_requests_per_endpoint = 3
1 week ago
2 weeks ago
def get_endpoint(self) -> str:
"""Get current endpoint with rate limiting"""
now = time.time()
time_since_last = now - self.last_request_time
if time_since_last < self.min_request_interval:
time.sleep(self.min_request_interval - time_since_last)
1 week ago
2 weeks ago
self.total_requests += 1
if self.total_requests >= self.max_requests_per_endpoint:
self.switch_endpoint()
self.total_requests = 0
1 week ago
2 weeks ago
self.last_request_time = time.time()
return self.current_endpoint
1 week ago
2 weeks ago
def switch_endpoint(self) -> str:
"""Switch to next available endpoint"""
current = self.current_endpoint
1 week ago
available_endpoints = [
ep for ep in self.endpoints if ep != current
]
2 weeks ago
if not available_endpoints:
raise SolanaAPIException("All endpoints exhausted")
1 week ago
2 weeks ago
self.current_endpoint = random.choice(available_endpoints)
logger.info(f"Switched to endpoint: {self.current_endpoint}")
return self.current_endpoint
1 week ago
def make_request(
endpoint_manager: RPCEndpointManager,
payload: dict,
retry_count: int = 3,
) -> dict:
2 weeks ago
"""
Makes a request with automatic endpoint switching and error handling.
"""
last_error = None
1 week ago
2 weeks ago
for attempt in range(retry_count):
try:
endpoint = endpoint_manager.get_endpoint()
1 week ago
2 weeks ago
response = requests.post(
endpoint,
json=payload,
timeout=10,
headers={"Content-Type": "application/json"},
1 week ago
verify=True, # Ensure SSL verification
2 weeks ago
)
1 week ago
2 weeks ago
if response.status_code != 200:
1 week ago
raise SolanaAPIException(
f"HTTP {response.status_code}: {response.text}"
)
2 weeks ago
data = response.json()
1 week ago
2 weeks ago
if "error" in data:
error_code = data["error"].get("code")
if error_code == 429: # Rate limit
1 week ago
logger.warning(
"Rate limit hit, switching endpoint..."
)
2 weeks ago
endpoint_manager.switch_endpoint()
1 week ago
time.sleep(2**attempt) # Exponential backoff
2 weeks ago
continue
1 week ago
2 weeks ago
if "message" in data["error"]:
1 week ago
raise SolanaAPIException(
f"RPC error: {data['error']['message']}"
)
2 weeks ago
return data
1 week ago
except (
requests.exceptions.SSLError,
requests.exceptions.ConnectionError,
) as e:
logger.warning(
f"Connection error with {endpoint}: {str(e)}"
)
2 weeks ago
endpoint_manager.switch_endpoint()
continue
1 week ago
2 weeks ago
except Exception as e:
last_error = e
logger.warning(f"Request failed: {str(e)}")
endpoint_manager.switch_endpoint()
time.sleep(1)
continue
1 week ago
raise SolanaAPIException(
f"All retry attempts failed. Last error: {str(last_error)}"
)
def fetch_wallet_transactions(
wallet_address: str, max_transactions: int = 10
) -> str:
2 weeks ago
"""
Fetches recent transactions for a given Solana wallet address.
1 week ago
2 weeks ago
Args:
wallet_address (str): The Solana wallet address to fetch transactions for
max_transactions (int, optional): Maximum number of transactions to fetch. Defaults to 10.
1 week ago
2 weeks ago
Returns:
str: JSON string containing transaction details
"""
try:
1 week ago
if (
not isinstance(wallet_address, str)
or len(wallet_address) != 44
):
raise ValueError(
f"Invalid Solana wallet address format: {wallet_address}"
)
2 weeks ago
1 week ago
if (
not isinstance(max_transactions, int)
or max_transactions < 1
):
raise ValueError(
"max_transactions must be a positive integer"
)
logger.info(
f"Fetching up to {max_transactions} transactions for wallet: {wallet_address}"
)
2 weeks ago
endpoint_manager = RPCEndpointManager(RPC_ENDPOINTS)
1 week ago
2 weeks ago
# Get transaction signatures
signatures_payload = {
"jsonrpc": "2.0",
"id": str(random.randint(1, 1000)),
"method": "getSignaturesForAddress",
1 week ago
"params": [wallet_address, {"limit": max_transactions}],
2 weeks ago
}
1 week ago
signatures_data = make_request(
endpoint_manager, signatures_payload
)
2 weeks ago
transactions = signatures_data.get("result", [])
if not transactions:
logger.info("No transactions found for this wallet")
1 week ago
return json.dumps(
{
"success": True,
"transactions": [],
"error": None,
"transaction_count": 0,
},
indent=2,
)
2 weeks ago
logger.info(f"Found {len(transactions)} transactions")
# Process transactions
enriched_transactions = []
for tx in transactions:
try:
tx_payload = {
"jsonrpc": "2.0",
"id": str(random.randint(1, 1000)),
"method": "getTransaction",
"params": [
tx["signature"],
1 week ago
{
"encoding": "json",
"maxSupportedTransactionVersion": 0,
},
],
2 weeks ago
}
1 week ago
2 weeks ago
tx_data = make_request(endpoint_manager, tx_payload)
1 week ago
2 weeks ago
if "result" in tx_data and tx_data["result"]:
result = tx_data["result"]
enriched_tx = {
"signature": tx["signature"],
"slot": tx["slot"],
"timestamp": tx.get("blockTime"),
"success": not tx.get("err"),
}
1 week ago
2 weeks ago
if "meta" in result:
enriched_tx["fee"] = result["meta"].get("fee")
1 week ago
if (
"preBalances" in result["meta"]
and "postBalances" in result["meta"]
):
enriched_tx["balance_change"] = sum(
result["meta"]["postBalances"]
) - sum(result["meta"]["preBalances"])
2 weeks ago
enriched_transactions.append(enriched_tx)
1 week ago
logger.info(
f"Processed transaction {tx['signature'][:8]}..."
)
2 weeks ago
except Exception as e:
1 week ago
logger.warning(
f"Failed to process transaction {tx['signature']}: {str(e)}"
)
2 weeks ago
continue
1 week ago
logger.info(
f"Successfully processed {len(enriched_transactions)} transactions"
)
return json.dumps(
{
"success": True,
"transactions": enriched_transactions,
"error": None,
"transaction_count": len(enriched_transactions),
},
indent=2,
)
2 weeks ago
except Exception as e:
error = TransactionError(
1 week ago
error_type="API_ERROR", message=str(e)
2 weeks ago
)
logger.error(f"Error: {error.message}")
1 week ago
return json.dumps(
{
"success": False,
"transactions": [],
"error": error.__dict__,
"transaction_count": 0,
},
indent=2,
)
2 weeks ago
if __name__ == "__main__":
# Example wallet address
wallet = "CtBLg4AX6LQfKVtPPUWqJyQ5cRfHydUwuZZ87rmojA1P"
1 week ago
2 weeks ago
try:
result = fetch_wallet_transactions(wallet)
print(result)
except Exception as e:
1 week ago
logger.error(f"Failed to fetch transactions: {str(e)}")