suix_getValidatorsApy
Returns the Annual Percentage Yield (APY) information for all validators in the current epoch on the Sui network.
Overview
The suix_getValidatorsApy
method provides comprehensive APY information for all active validators in the current epoch. This data is essential for stakers to make informed decisions about validator selection, staking rewards optimization, and understanding the economic incentives within the Sui network. The APY calculation takes into account validator commission rates, network rewards, and historical performance.
Parameters
This method takes no parameters.
Returns
Returns an array of validator APY objects, one for each active validator in the current epoch.
Field | Type | Description |
---|---|---|
address | string | Validator's Sui address |
apy | number | Annual Percentage Yield as a decimal (e.g., 0.05 = 5%) |
APY Calculation
The APY is calculated based on:
- Network Rewards: Base rewards distributed to all validators
- Commission Rate: The percentage the validator takes as commission
- Pool Performance: Historical performance and uptime of the validator
- Network Participation: Overall network staking participation rate
Code Examples
- cURL
- JavaScript
- Python
# Get APY information for all validators
curl -X POST https://sui-mainnet.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "suix_getValidatorsApy",
"params": [],
"id": 1
}'
import { SuiClient } from '@mysten/sui.js/client';
const client = new SuiClient({
url: 'https://sui-mainnet.dwellir.com/YOUR_API_KEY'
});
// Get all validators APY
async function getValidatorsApy() {
try {
const apyData = await client.getValidatorsApy();
console.log(`Found APY data for ${apyData.length} validators`);
// Sort by APY descending
const sortedValidators = apyData.sort((a, b) => b.apy - a.apy);
console.log('Top 10 validators by APY:');
sortedValidators.slice(0, 10).forEach((validator, index) => {
console.log(`${index + 1}. ${validator.address}: ${(validator.apy * 100).toFixed(2)}%`);
});
return apyData;
} catch (error) {
console.error('Failed to get validators APY:', error);
return [];
}
}
// Enhanced validator analysis
async function analyzeValidatorRewards() {
const [apyData, validatorSummary] = await Promise.all([
client.getValidatorsApy(),
client.getLatestSuiSystemState()
]);
const validators = validatorSummary.activeValidators;
// Combine APY data with validator details
const enrichedValidators = apyData.map(apyInfo => {
const validatorDetails = validators.find(v => v.suiAddress === apyInfo.address);
return {
address: apyInfo.address,
apy: apyInfo.apy,
name: validatorDetails?.name || 'Unknown',
description: validatorDetails?.description || '',
imageUrl: validatorDetails?.imageUrl || '',
projectUrl: validatorDetails?.projectUrl || '',
commissionRate: validatorDetails?.commissionRate ?
parseInt(validatorDetails.commissionRate) / 100 : 0, // Convert basis points to percentage
stakingPoolSuiBalance: validatorDetails?.stakingPoolSuiBalance || '0',
delegationStakingPool: validatorDetails?.delegationStakingPool || {},
votingPower: validatorDetails?.votingPower || '0'
};
});
return enrichedValidators;
}
// Staking strategy optimizer
class StakingOptimizer {
constructor(client) {
this.client = client;
}
async findOptimalValidators(stakingAmount, preferences = {}) {
const {
minApy = 0,
maxCommission = 0.1, // 10%
minPoolSize = 1000000000000, // 1M SUI in MIST
diversificationCount = 3,
riskTolerance = 'medium' // low, medium, high
} = preferences;
const validators = await analyzeValidatorRewards();
// Filter validators based on preferences
let eligibleValidators = validators.filter(v => {
return v.apy >= minApy &&
v.commissionRate <= maxCommission &&
parseInt(v.stakingPoolSuiBalance) >= minPoolSize;
});
// Apply risk-based filtering
if (riskTolerance === 'low') {
// Prefer established validators with consistent performance
eligibleValidators = eligibleValidators.filter(v =>
parseInt(v.stakingPoolSuiBalance) >= 10000000000000 && // 10M+ SUI
v.commissionRate <= 0.05 // <= 5% commission
);
} else if (riskTolerance === 'high') {
// Allow newer validators with potentially higher rewards
eligibleValidators = eligibleValidators.filter(v => v.apy >= 0.04); // >= 4% APY
}
// Sort by APY and apply diversification
eligibleValidators.sort((a, b) => b.apy - a.apy);
const recommendations = [];
const selectedValidators = new Set();
// Diversification strategy
for (let i = 0; i < diversificationCount && i < eligibleValidators.length; i++) {
let candidate = eligibleValidators[i];
// Avoid selecting validators that are too similar (same operator, etc.)
if (!selectedValidators.has(candidate.address)) {
const allocation = this.calculateAllocation(
stakingAmount,
i,
diversificationCount,
candidate.apy
);
recommendations.push({
validator: candidate,
recommendedAmount: allocation,
expectedAnnualReturn: allocation * candidate.apy,
reasoning: this.getReasoningForValidator(candidate, i)
});
selectedValidators.add(candidate.address);
}
}
const totalExpectedReturn = recommendations.reduce(
(sum, rec) => sum + rec.expectedAnnualReturn, 0
);
return {
stakingAmount,
preferences,
recommendations,
totalExpectedReturn,
weightedAverageApy: totalExpectedReturn / stakingAmount,
diversificationScore: recommendations.length / diversificationCount
};
}
calculateAllocation(totalAmount, validatorIndex, totalValidators, apy) {
// Weighted allocation based on APY and diversification
const baseAllocation = totalAmount / totalValidators;
if (validatorIndex === 0) {
// Allocate more to the top performer
return Math.floor(baseAllocation * 1.5);
} else if (validatorIndex === 1) {
return Math.floor(baseAllocation * 1.25);
} else {
return Math.floor(baseAllocation * 0.75);
}
}
getReasoningForValidator(validator, rank) {
const reasons = [];
if (rank === 0) {
reasons.push('Highest APY among eligible validators');
}
if (validator.commissionRate <= 0.03) {
reasons.push('Low commission rate');
}
if (parseInt(validator.stakingPoolSuiBalance) >= 10000000000000) {
reasons.push('Large, established staking pool');
}
if (validator.apy >= 0.06) {
reasons.push('Above-average yields');
}
return reasons.join(', ');
}
}
// APY tracking and alerts
class APYTracker {
constructor(client) {
this.client = client;
this.previousAPYData = new Map();
this.alertThresholds = {
significantDrop: 0.01, // 1% drop
significantIncrease: 0.01, // 1% increase
absoluteMinimum: 0.02 // 2% minimum APY
};
}
async trackAPYChanges() {
const currentAPYData = await this.client.getValidatorsApy();
if (this.previousAPYData.size === 0) {
// First run - just store the data
currentAPYData.forEach(validator => {
this.previousAPYData.set(validator.address, validator.apy);
});
console.log(`📊 Initial APY data collected for ${currentAPYData.length} validators`);
return [];
}
const changes = [];
currentAPYData.forEach(validator => {
const previousAPY = this.previousAPYData.get(validator.address);
if (previousAPY !== undefined) {
const apyChange = validator.apy - previousAPY;
const percentChange = (apyChange / previousAPY) * 100;
let alertType = null;
if (Math.abs(apyChange) >= this.alertThresholds.significantDrop) {
alertType = apyChange > 0 ? 'SIGNIFICANT_INCREASE' : 'SIGNIFICANT_DROP';
}
if (validator.apy < this.alertThresholds.absoluteMinimum) {
alertType = 'LOW_APY_WARNING';
}
if (alertType || Math.abs(percentChange) >= 5) { // 5% change threshold for logging
changes.push({
validatorAddress: validator.address,
previousAPY: previousAPY,
currentAPY: validator.apy,
absoluteChange: apyChange,
percentChange: percentChange,
alertType: alertType
});
}
}
// Update stored APY
this.previousAPYData.set(validator.address, validator.apy);
});
return changes;
}
async startMonitoring(intervalMinutes = 60) {
console.log(`🔍 Starting APY monitoring (checking every ${intervalMinutes} minutes)`);
const monitor = async () => {
try {
const changes = await this.trackAPYChanges();
if (changes.length > 0) {
console.log(`📈 APY Changes detected for ${changes.length} validators:`);
changes.forEach(change => {
const direction = change.absoluteChange > 0 ? '📈' : '📉';
console.log(
`${direction} ${change.validatorAddress.substring(0, 16)}...: ` +
`${(change.previousAPY * 100).toFixed(2)}% → ${(change.currentAPY * 100).toFixed(2)}% ` +
`(${change.percentChange > 0 ? '+' : ''}${change.percentChange.toFixed(2)}%)`
);
if (change.alertType) {
console.log(`🚨 Alert: ${change.alertType}`);
}
});
} else {
console.log('📊 No significant APY changes detected');
}
} catch (error) {
console.error('Error in APY monitoring:', error);
}
};
// Initial check
await monitor();
// Set up interval
const intervalId = setInterval(monitor, intervalMinutes * 60 * 1000);
return () => {
clearInterval(intervalId);
console.log('APY monitoring stopped');
};
}
}
// Validator performance analyzer
async function analyzeValidatorPerformance() {
const validators = await analyzeValidatorRewards();
const analysis = {
totalValidators: validators.length,
apyStatistics: {
highest: Math.max(...validators.map(v => v.apy)),
lowest: Math.min(...validators.map(v => v.apy)),
average: validators.reduce((sum, v) => sum + v.apy, 0) / validators.length,
median: validators.map(v => v.apy).sort()[Math.floor(validators.length / 2)]
},
commissionAnalysis: {
averageCommission: validators.reduce((sum, v) => sum + v.commissionRate, 0) / validators.length,
lowCommissionCount: validators.filter(v => v.commissionRate <= 0.05).length,
highCommissionCount: validators.filter(v => v.commissionRate >= 0.1).length
},
stakingPoolAnalysis: {
totalStakedSUI: validators.reduce((sum, v) => sum + parseInt(v.stakingPoolSuiBalance), 0),
averagePoolSize: validators.reduce((sum, v) => sum + parseInt(v.stakingPoolSuiBalance), 0) / validators.length,
largePoolCount: validators.filter(v => parseInt(v.stakingPoolSuiBalance) >= 10000000000000).length
},
topPerformers: validators
.sort((a, b) => b.apy - a.apy)
.slice(0, 10)
.map(v => ({
name: v.name,
address: v.address,
apy: v.apy,
commission: v.commissionRate,
poolSize: parseInt(v.stakingPoolSuiBalance)
})),
underperformers: validators
.filter(v => v.apy < analysis.apyStatistics.average * 0.8) // 20% below average
.sort((a, b) => a.apy - b.apy)
.slice(0, 5)
.map(v => ({
name: v.name,
address: v.address,
apy: v.apy,
commission: v.commissionRate,
poolSize: parseInt(v.stakingPoolSuiBalance)
}))
};
return analysis;
}
// Staking rewards calculator
class StakingCalculator {
constructor(client) {
this.client = client;
}
async calculateRewards(stakingAmount, validatorAddress, timePeriods = [30, 90, 365]) {
const validators = await analyzeValidatorRewards();
const validator = validators.find(v => v.address === validatorAddress);
if (!validator) {
throw new Error(`Validator ${validatorAddress} not found`);
}
const calculations = {
validator: {
name: validator.name,
address: validator.address,
apy: validator.apy,
commission: validator.commissionRate
},
stakingAmount: stakingAmount,
projections: []
};
timePeriods.forEach(days => {
const dailyRate = validator.apy / 365;
const compoundedReturn = stakingAmount * Math.pow(1 + dailyRate, days);
const simpleReturn = stakingAmount * (1 + (validator.apy * days / 365));
calculations.projections.push({
days: days,
period: this.formatTimePeriod(days),
compoundedRewards: compoundedReturn - stakingAmount,
simpleRewards: simpleReturn - stakingAmount,
totalCompounded: compoundedReturn,
totalSimple: simpleReturn,
effectiveAPY: Math.pow(compoundedReturn / stakingAmount, 365 / days) - 1
});
});
return calculations;
}
formatTimePeriod(days) {
if (days < 30) return `${days} days`;
if (days < 365) return `${Math.round(days / 30)} months`;
return `${Math.round(days / 365 * 10) / 10} years`;
}
async compareValidators(stakingAmount, validatorAddresses, period = 365) {
const validators = await analyzeValidatorRewards();
const comparisons = [];
for (const address of validatorAddresses) {
const validator = validators.find(v => v.address === address);
if (validator) {
const rewards = await this.calculateRewards(stakingAmount, address, [period]);
comparisons.push({
validator: validator,
projection: rewards.projections[0],
riskFactors: this.assessRiskFactors(validator)
});
}
}
// Sort by total returns
comparisons.sort((a, b) => b.projection.totalCompounded - a.projection.totalCompounded);
return {
stakingAmount,
period,
comparisons,
bestChoice: comparisons[0],
recommendation: this.generateRecommendation(comparisons)
};
}
assessRiskFactors(validator) {
const risks = [];
if (validator.commissionRate > 0.08) {
risks.push('High commission rate');
}
if (parseInt(validator.stakingPoolSuiBalance) < 1000000000000) {
risks.push('Small staking pool');
}
if (validator.apy > 0.08) {
risks.push('Unusually high APY (may indicate higher risk)');
}
if (validator.apy < 0.03) {
risks.push('Low APY compared to network average');
}
return risks;
}
generateRecommendation(comparisons) {
if (comparisons.length === 0) return 'No validators to compare';
const best = comparisons[0];
const riskLevel = best.riskFactors.length === 0 ? 'Low' :
best.riskFactors.length <= 2 ? 'Medium' : 'High';
return {
recommended: best.validator.address,
name: best.validator.name,
expectedReturn: best.projection.compoundedRewards,
apy: best.validator.apy,
riskLevel: riskLevel,
reasoning: riskLevel === 'Low' ?
'Best returns with minimal risk factors' :
'Highest returns but consider the risk factors'
};
}
}
// Usage examples
// Get basic APY data
const apyData = await getValidatorsApy();
console.log(`APY data for ${apyData.length} validators`);
// Detailed validator analysis
const detailedValidators = await analyzeValidatorRewards();
console.log('Detailed validator data loaded');
// Find optimal staking strategy
const optimizer = new StakingOptimizer(client);
const strategy = await optimizer.findOptimalValidators(10000000000000, { // 10K SUI in MIST
minApy: 0.04,
maxCommission: 0.07,
diversificationCount: 3,
riskTolerance: 'medium'
});
console.log('\nOptimal Staking Strategy:');
console.log(`Expected Annual Return: ${(strategy.totalExpectedReturn / 1000000000).toFixed(2)} SUI`);
console.log(`Weighted Average APY: ${(strategy.weightedAverageApy * 100).toFixed(2)}%`);
strategy.recommendations.forEach((rec, index) => {
console.log(`\n${index + 1}. ${rec.validator.name}`);
console.log(` Amount: ${(rec.recommendedAmount / 1000000000).toFixed(2)} SUI`);
console.log(` APY: ${(rec.validator.apy * 100).toFixed(2)}%`);
console.log(` Expected Return: ${(rec.expectedAnnualReturn / 1000000000).toFixed(2)} SUI`);
console.log(` Reasoning: ${rec.reasoning}`);
});
// Start APY monitoring
const apyTracker = new APYTracker(client);
const stopMonitoring = await apyTracker.startMonitoring(120); // Check every 2 hours
// Performance analysis
const performance = await analyzeValidatorPerformance();
console.log('\nNetwork Performance Analysis:');
console.log(`Total Validators: ${performance.totalValidators}`);
console.log(`Average APY: ${(performance.apyStatistics.average * 100).toFixed(2)}%`);
console.log(`Highest APY: ${(performance.apyStatistics.highest * 100).toFixed(2)}%`);
console.log(`Average Commission: ${(performance.commissionAnalysis.averageCommission * 100).toFixed(2)}%`);
// Staking calculator
const calculator = new StakingCalculator(client);
const topValidator = detailedValidators.sort((a, b) => b.apy - a.apy)[0];
const rewardProjections = await calculator.calculateRewards(
5000000000000, // 5K SUI in MIST
topValidator.address,
[30, 90, 180, 365]
);
console.log(`\nReward Projections for ${topValidator.name}:`);
rewardProjections.projections.forEach(proj => {
console.log(`${proj.period}: ${(proj.compoundedRewards / 1000000000).toFixed(2)} SUI rewards`);
});
// Stop monitoring after demo
setTimeout(stopMonitoring, 300000); // 5 minutes
import requests
import json
import time
import math
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass, field
from datetime import datetime, timedelta
import threading
@dataclass
class ValidatorAPY:
address: str
apy: float
@dataclass
class EnrichedValidator:
address: str
apy: float
name: str = ""
description: str = ""
image_url: str = ""
project_url: str = ""
commission_rate: float = 0.0
staking_pool_balance: int = 0
voting_power: int = 0
@dataclass
class StakingRecommendation:
validator: EnrichedValidator
recommended_amount: int
expected_annual_return: float
reasoning: str
class SuiValidatorAPYClient:
def __init__(self, rpc_url: str):
self.rpc_url = rpc_url
def get_validators_apy(self) -> List[ValidatorAPY]:
"""Get APY data for all validators"""
payload = {
"jsonrpc": "2.0",
"method": "suix_getValidatorsApy",
"params": [],
"id": 1
}
try:
response = requests.post(
self.rpc_url,
headers={'Content-Type': 'application/json'},
data=json.dumps(payload),
timeout=30
)
result = response.json()
if 'error' in result:
print(f"RPC Error: {result['error']}")
return []
apy_data = result['result']
return [ValidatorAPY(address=item['address'], apy=item['apy']) for item in apy_data]
except Exception as e:
print(f"Error fetching validators APY: {e}")
return []
def get_system_state(self) -> Optional[Dict[str, Any]]:
"""Get latest system state for validator details"""
payload = {
"jsonrpc": "2.0",
"method": "suix_getLatestSuiSystemState",
"params": [],
"id": 1
}
try:
response = requests.post(
self.rpc_url,
headers={'Content-Type': 'application/json'},
data=json.dumps(payload),
timeout=30
)
result = response.json()
if 'error' in result:
print(f"RPC Error getting system state: {result['error']}")
return None
return result['result']
except Exception as e:
print(f"Error fetching system state: {e}")
return None
def analyze_validator_rewards(self) -> List[EnrichedValidator]:
"""Get comprehensive validator information including APY"""
apy_data = self.get_validators_apy()
system_state = self.get_system_state()
if not apy_data or not system_state:
return []
validators = system_state.get('activeValidators', [])
enriched_validators = []
for apy_info in apy_data:
# Find corresponding validator details
validator_details = None
for validator in validators:
if validator['suiAddress'] == apy_info.address:
validator_details = validator
break
if validator_details:
enriched_validators.append(EnrichedValidator(
address=apy_info.address,
apy=apy_info.apy,
name=validator_details.get('name', 'Unknown'),
description=validator_details.get('description', ''),
image_url=validator_details.get('imageUrl', ''),
project_url=validator_details.get('projectUrl', ''),
commission_rate=int(validator_details.get('commissionRate', 0)) / 10000, # Convert basis points
staking_pool_balance=int(validator_details.get('stakingPoolSuiBalance', 0)),
voting_power=int(validator_details.get('votingPower', 0))
))
else:
# Fallback for validators without details
enriched_validators.append(EnrichedValidator(
address=apy_info.address,
apy=apy_info.apy
))
return enriched_validators
class StakingOptimizer:
def __init__(self, client: SuiValidatorAPYClient):
self.client = client
def find_optimal_validators(
self,
staking_amount: int,
min_apy: float = 0.0,
max_commission: float = 0.1,
min_pool_size: int = 1000000000000, # 1M SUI in MIST
diversification_count: int = 3,
risk_tolerance: str = 'medium'
) -> Dict[str, Any]:
"""Find optimal validators for staking strategy"""
validators = self.client.analyze_validator_rewards()
if not validators:
return {'error': 'No validator data available'}
# Filter validators based on preferences
eligible_validators = [
v for v in validators
if v.apy >= min_apy and
v.commission_rate <= max_commission and
v.staking_pool_balance >= min_pool_size
]
# Apply risk-based filtering
if risk_tolerance == 'low':
eligible_validators = [
v for v in eligible_validators
if v.staking_pool_balance >= 10000000000000 and # 10M+ SUI
v.commission_rate <= 0.05 # <= 5% commission
]
elif risk_tolerance == 'high':
eligible_validators = [
v for v in eligible_validators
if v.apy >= 0.04 # >= 4% APY
]
# Sort by APY descending
eligible_validators.sort(key=lambda v: v.apy, reverse=True)
if not eligible_validators:
return {'error': 'No validators meet the specified criteria'}
# Generate recommendations with diversification
recommendations = []
selected_count = min(diversification_count, len(eligible_validators))
for i in range(selected_count):
validator = eligible_validators[i]
allocation = self._calculate_allocation(staking_amount, i, selected_count, validator.apy)
recommendations.append(StakingRecommendation(
validator=validator,
recommended_amount=allocation,
expected_annual_return=allocation * validator.apy,
reasoning=self._get_reasoning_for_validator(validator, i)
))
total_expected_return = sum(rec.expected_annual_return for rec in recommendations)
return {
'staking_amount': staking_amount,
'recommendations': recommendations,
'total_expected_return': total_expected_return,
'weighted_average_apy': total_expected_return / staking_amount,
'diversification_score': len(recommendations) / diversification_count
}
def _calculate_allocation(self, total_amount: int, validator_index: int, total_validators: int, apy: float) -> int:
"""Calculate allocation amount for each validator"""
base_allocation = total_amount // total_validators
if validator_index == 0:
return int(base_allocation * 1.5)
elif validator_index == 1:
return int(base_allocation * 1.25)
else:
return int(base_allocation * 0.75)
def _get_reasoning_for_validator(self, validator: EnrichedValidator, rank: int) -> str:
"""Generate reasoning for validator selection"""
reasons = []
if rank == 0:
reasons.append('Highest APY among eligible validators')
if validator.commission_rate <= 0.03:
reasons.append('Low commission rate')
if validator.staking_pool_balance >= 10000000000000:
reasons.append('Large, established staking pool')
if validator.apy >= 0.06:
reasons.append('Above-average yields')
return ', '.join(reasons) if reasons else 'Meets basic criteria'
class APYTracker:
def __init__(self, client: SuiValidatorAPYClient):
self.client = client
self.previous_apy_data = {}
self.alert_thresholds = {
'significant_drop': 0.01, # 1% drop
'significant_increase': 0.01, # 1% increase
'absolute_minimum': 0.02 # 2% minimum APY
}
self.is_monitoring = False
self.monitor_thread = None
def track_apy_changes(self) -> List[Dict[str, Any]]:
"""Track APY changes and generate alerts"""
current_apy_data = self.client.get_validators_apy()
if not self.previous_apy_data:
# First run - store initial data
for validator in current_apy_data:
self.previous_apy_data[validator.address] = validator.apy
print(f"📊 Initial APY data collected for {len(current_apy_data)} validators")
return []
changes = []
for validator in current_apy_data:
previous_apy = self.previous_apy_data.get(validator.address)
if previous_apy is not None:
apy_change = validator.apy - previous_apy
percent_change = (apy_change / previous_apy) * 100 if previous_apy > 0 else 0
alert_type = None
if abs(apy_change) >= self.alert_thresholds['significant_drop']:
alert_type = 'SIGNIFICANT_INCREASE' if apy_change > 0 else 'SIGNIFICANT_DROP'
if validator.apy < self.alert_thresholds['absolute_minimum']:
alert_type = 'LOW_APY_WARNING'
if alert_type or abs(percent_change) >= 5: # 5% change threshold
changes.append({
'validator_address': validator.address,
'previous_apy': previous_apy,
'current_apy': validator.apy,
'absolute_change': apy_change,
'percent_change': percent_change,
'alert_type': alert_type
})
# Update stored APY
self.previous_apy_data[validator.address] = validator.apy
return changes
def start_monitoring(self, interval_minutes: int = 60, max_duration_hours: int = 24):
"""Start monitoring APY changes"""
if self.is_monitoring:
print("APY monitoring already in progress")
return
print(f"🔍 Starting APY monitoring (checking every {interval_minutes} minutes)")
self.is_monitoring = True
def monitor_loop():
start_time = time.time()
max_duration = max_duration_hours * 3600
while self.is_monitoring and (time.time() - start_time) < max_duration:
try:
changes = self.track_apy_changes()
if changes:
print(f"📈 APY changes detected for {len(changes)} validators:")
for change in changes:
direction = '📈' if change['absolute_change'] > 0 else '📉'
print(
f"{direction} {change['validator_address'][:16]}...: "
f"{change['previous_apy']*100:.2f}% → {change['current_apy']*100:.2f}% "
f"({'+'if change['percent_change'] > 0 else ''}{change['percent_change']:.2f}%)"
)
if change['alert_type']:
print(f"🚨 Alert: {change['alert_type']}")
else:
print("📊 No significant APY changes detected")
time.sleep(interval_minutes * 60)
except Exception as e:
print(f"Error in APY monitoring: {e}")
time.sleep(interval_minutes * 60)
self.is_monitoring = False
print("APY monitoring completed")
self.monitor_thread = threading.Thread(target=monitor_loop, daemon=True)
self.monitor_thread.start()
def stop_monitoring(self):
"""Stop APY monitoring"""
self.is_monitoring = False
if self.monitor_thread:
self.monitor_thread.join(timeout=5)
print("APY monitoring stopped")
class ValidatorAnalyzer:
def __init__(self, client: SuiValidatorAPYClient):
self.client = client
def analyze_validator_performance(self) -> Dict[str, Any]:
"""Comprehensive validator performance analysis"""
validators = self.client.analyze_validator_rewards()
if not validators:
return {'error': 'No validator data available'}
apys = [v.apy for v in validators]
commissions = [v.commission_rate for v in validators]
pool_balances = [v.staking_pool_balance for v in validators]
analysis = {
'total_validators': len(validators),
'apy_statistics': {
'highest': max(apys),
'lowest': min(apys),
'average': sum(apys) / len(apys),
'median': sorted(apys)[len(apys) // 2]
},
'commission_analysis': {
'average_commission': sum(commissions) / len(commissions),
'low_commission_count': len([c for c in commissions if c <= 0.05]),
'high_commission_count': len([c for c in commissions if c >= 0.1])
},
'staking_pool_analysis': {
'total_staked_sui': sum(pool_balances),
'average_pool_size': sum(pool_balances) / len(pool_balances),
'large_pool_count': len([b for b in pool_balances if b >= 10000000000000])
},
'top_performers': sorted(validators, key=lambda v: v.apy, reverse=True)[:10],
'underperformers': sorted([v for v in validators if v.apy < sum(apys)/len(apys)*0.8],
key=lambda v: v.apy)[:5]
}
return analysis
class StakingCalculator:
def __init__(self, client: SuiValidatorAPYClient):
self.client = client
def calculate_rewards(
self,
staking_amount: int,
validator_address: str,
time_periods: List[int] = [30, 90, 365]
) -> Dict[str, Any]:
"""Calculate staking rewards for specific validator and time periods"""
validators = self.client.analyze_validator_rewards()
validator = next((v for v in validators if v.address == validator_address), None)
if not validator:
raise ValueError(f"Validator {validator_address} not found")
calculations = {
'validator': {
'name': validator.name,
'address': validator.address,
'apy': validator.apy,
'commission': validator.commission_rate
},
'staking_amount': staking_amount,
'projections': []
}
for days in time_periods:
daily_rate = validator.apy / 365
compounded_return = staking_amount * math.pow(1 + daily_rate, days)
simple_return = staking_amount * (1 + (validator.apy * days / 365))
calculations['projections'].append({
'days': days,
'period': self._format_time_period(days),
'compounded_rewards': compounded_return - staking_amount,
'simple_rewards': simple_return - staking_amount,
'total_compounded': compounded_return,
'total_simple': simple_return,
'effective_apy': math.pow(compounded_return / staking_amount, 365 / days) - 1
})
return calculations
def compare_validators(
self,
staking_amount: int,
validator_addresses: List[str],
period: int = 365
) -> Dict[str, Any]:
"""Compare multiple validators for staking rewards"""
validators = self.client.analyze_validator_rewards()
comparisons = []
for address in validator_addresses:
validator = next((v for v in validators if v.address == address), None)
if validator:
rewards = self.calculate_rewards(staking_amount, address, [period])
comparisons.append({
'validator': validator,
'projection': rewards['projections'][0],
'risk_factors': self._assess_risk_factors(validator)
})
# Sort by total returns
comparisons.sort(key=lambda x: x['projection']['total_compounded'], reverse=True)
return {
'staking_amount': staking_amount,
'period': period,
'comparisons': comparisons,
'best_choice': comparisons[0] if comparisons else None,
'recommendation': self._generate_recommendation(comparisons)
}
def _format_time_period(self, days: int) -> str:
"""Format time period for display"""
if days < 30:
return f"{days} days"
elif days < 365:
return f"{round(days / 30)} months"
else:
return f"{round(days / 365 * 10) / 10} years"
def _assess_risk_factors(self, validator: EnrichedValidator) -> List[str]:
"""Assess risk factors for a validator"""
risks = []
if validator.commission_rate > 0.08:
risks.append('High commission rate')
if validator.staking_pool_balance < 1000000000000:
risks.append('Small staking pool')
if validator.apy > 0.08:
risks.append('Unusually high APY (may indicate higher risk)')
if validator.apy < 0.03:
risks.append('Low APY compared to network average')
return risks
def _generate_recommendation(self, comparisons: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Generate recommendation based on comparisons"""
if not comparisons:
return {'message': 'No validators to compare'}
best = comparisons[0]
risk_level = ('Low' if len(best['risk_factors']) == 0 else
'Medium' if len(best['risk_factors']) <= 2 else 'High')
return {
'recommended': best['validator'].address,
'name': best['validator'].name,
'expected_return': best['projection']['compounded_rewards'],
'apy': best['validator'].apy,
'risk_level': risk_level,
'reasoning': ('Best returns with minimal risk factors' if risk_level == 'Low' else
'Highest returns but consider the risk factors')
}
# Usage examples
client = SuiValidatorAPYClient('https://sui-mainnet.dwellir.com/YOUR_API_KEY')
# Example 1: Get basic APY data
print("Fetching validator APY data...")
apy_data = client.get_validators_apy()
if apy_data:
print(f"Found APY data for {len(apy_data)} validators")
# Sort by APY and show top 5
top_validators = sorted(apy_data, key=lambda v: v.apy, reverse=True)[:5]
print("\nTop 5 validators by APY:")
for i, validator in enumerate(top_validators, 1):
print(f"{i}. {validator.address[:16]}...: {validator.apy*100:.2f}%")
# Example 2: Detailed validator analysis
print("\nAnalyzing validator rewards...")
detailed_validators = client.analyze_validator_rewards()
if detailed_validators:
print(f"Loaded detailed data for {len(detailed_validators)} validators")
# Show validators with names
named_validators = [v for v in detailed_validators if v.name and v.name != 'Unknown']
print(f"Validators with names: {len(named_validators)}")
for validator in named_validators[:3]:
print(f" {validator.name}: {validator.apy*100:.2f}% APY, {validator.commission_rate*100:.1f}% commission")
# Example 3: Staking optimization
print("\nOptimizing staking strategy...")
optimizer = StakingOptimizer(client)
strategy = optimizer.find_optimal_validators(
staking_amount=10000000000000, # 10K SUI in MIST
min_apy=0.04,
max_commission=0.07,
diversification_count=3,
risk_tolerance='medium'
)
if 'error' not in strategy:
print(f"Expected Annual Return: {strategy['total_expected_return'] / 1000000000:.2f} SUI")
print(f"Weighted Average APY: {strategy['weighted_average_apy']*100:.2f}%")
print("\nRecommendations:")
for i, rec in enumerate(strategy['recommendations'], 1):
print(f"{i}. {rec.validator.name or rec.validator.address[:16]+'...'}")
print(f" Amount: {rec.recommended_amount / 1000000000:.2f} SUI")
print(f" APY: {rec.validator.apy*100:.2f}%")
print(f" Expected Return: {rec.expected_annual_return / 1000000000:.2f} SUI")
print(f" Reasoning: {rec.reasoning}")
print()
# Example 4: Performance analysis
print("Analyzing network performance...")
analyzer = ValidatorAnalyzer(client)
performance = analyzer.analyze_validator_performance()
if 'error' not in performance:
print(f"Total Validators: {performance['total_validators']}")
print(f"Average APY: {performance['apy_statistics']['average']*100:.2f}%")
print(f"Highest APY: {performance['apy_statistics']['highest']*100:.2f}%")
print(f"Average Commission: {performance['commission_analysis']['average_commission']*100:.2f}%")
print(f"Large pools (>10M SUI): {performance['staking_pool_analysis']['large_pool_count']}")
# Example 5: Staking calculator
print("\nCalculating staking rewards...")
calculator = StakingCalculator(client)
if detailed_validators:
top_validator = max(detailed_validators, key=lambda v: v.apy)
try:
reward_projections = calculator.calculate_rewards(
staking_amount=5000000000000, # 5K SUI in MIST
validator_address=top_validator.address,
time_periods=[30, 90, 180, 365]
)
print(f"Reward projections for {top_validator.name or 'Top Validator'}:")
for proj in reward_projections['projections']:
print(f" {proj['period']}: {proj['compounded_rewards'] / 1000000000:.2f} SUI rewards")
except Exception as e:
print(f"Error calculating rewards: {e}")
# Example 6: APY monitoring (runs for 2 minutes as demo)
print("\nStarting APY monitoring demo...")
apy_tracker = APYTracker(client)
apy_tracker.start_monitoring(interval_minutes=1, max_duration_hours=1) # 1 minute intervals for demo
# Let it run for a bit
time.sleep(120) # 2 minutes
# Stop monitoring
apy_tracker.stop_monitoring()
print("Demo completed.")
Response Example
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"address": "0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f",
"apy": 0.0543
},
{
"address": "0x1a2b3c4d5e6f789012345678901234567890123456789012345678901234567890",
"apy": 0.0521
},
{
"address": "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55",
"apy": 0.0498
},
{
"address": "0x7f8e9d1a2b3c4e5f6789abcdef0123456789abcdef0123456789abcdef012345",
"apy": 0.0476
},
{
"address": "0x9f234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef0",
"apy": 0.0455
}
]
}
Common Use Cases
1. Validator Selection for Staking
async function selectBestValidator(stakingAmount, preferences = {}) {
const { maxCommission = 0.08, minPoolSize = 5000000000000 } = preferences;
const validators = await analyzeValidatorRewards();
// Filter and rank validators
const eligibleValidators = validators
.filter(v =>
v.commissionRate <= maxCommission &&
v.stakingPoolSuiBalance >= minPoolSize
)
.sort((a, b) => b.apy - a.apy);
if (eligibleValidators.length === 0) {
throw new Error('No validators meet criteria');
}
const topValidator = eligibleValidators[0];
const expectedReturn = stakingAmount * topValidator.apy;
return {
validator: topValidator,
expectedAnnualReturn: expectedReturn,
ranking: `#1 of ${eligibleValidators.length} eligible validators`,
competitiveAdvantage: topValidator.apy - (eligibleValidators[1]?.apy || 0)
};
}
2. Staking Portfolio Diversification
async function createDiversifiedStakingPortfolio(totalAmount, riskLevel = 'balanced') {
const validators = await analyzeValidatorRewards();
let portfolio = [];
if (riskLevel === 'conservative') {
// Focus on established validators with lower risk
const safeValidators = validators
.filter(v => v.commissionRate <= 0.05 && v.stakingPoolSuiBalance >= 10000000000000)
.sort((a, b) => b.apy - a.apy)
.slice(0, 2);
portfolio = safeValidators.map((v, i) => ({
validator: v,
allocation: i === 0 ? totalAmount * 0.6 : totalAmount * 0.4,
risk: 'Low'
}));
} else if (riskLevel === 'aggressive') {
// Higher APY focus with more risk tolerance
const highYieldValidators = validators
.filter(v => v.apy >= 0.05)
.sort((a, b) => b.apy - a.apy)
.slice(0, 3);
portfolio = highYieldValidators.map((v, i) => ({
validator: v,
allocation: totalAmount / 3,
risk: 'High'
}));
} else { // balanced
const balancedValidators = validators
.filter(v => v.commissionRate <= 0.07 && v.apy >= 0.035)
.sort((a, b) => b.apy - a.apy)
.slice(0, 3);
portfolio = balancedValidators.map((v, i) => ({
validator: v,
allocation: i === 0 ? totalAmount * 0.5 : totalAmount * 0.25,
risk: 'Medium'
}));
}
const totalExpectedReturn = portfolio.reduce(
(sum, p) => sum + (p.allocation * p.validator.apy), 0
);
return {
portfolio,
totalExpectedReturn,
weightedAPY: totalExpectedReturn / totalAmount,
riskLevel
};
}
3. Yield Farming Strategy Optimization
async function optimizeYieldStrategy(availableAmount, targetAPY = 0.05) {
const validators = await analyzeValidatorRewards();
// Find validators that meet or exceed target APY
const qualifyingValidators = validators
.filter(v => v.apy >= targetAPY)
.sort((a, b) => {
// Sort by APY but consider commission as secondary factor
const apyDiff = b.apy - a.apy;
if (Math.abs(apyDiff) < 0.005) { // If APY is similar (within 0.5%)
return a.commissionRate - b.commissionRate; // Prefer lower commission
}
return apyDiff;
});
if (qualifyingValidators.length === 0) {
return {
feasible: false,
message: `No validators currently offer ${targetAPY * 100}% APY or higher`,
bestAvailable: Math.max(...validators.map(v => v.apy))
};
}
// Calculate optimal allocation
const strategy = {
feasible: true,
validators: qualifyingValidators.slice(0, 5), // Top 5 options
recommendation: {
primaryValidator: qualifyingValidators[0],
allocation: availableAmount,
expectedReturn: availableAmount * qualifyingValidators[0].apy,
riskAssessment: assessValidatorRisk(qualifyingValidators[0])
},
alternatives: qualifyingValidators.slice(1, 4).map(v => ({
validator: v,
apyDifference: qualifyingValidators[0].apy - v.apy,
commissionAdvantage: v.commissionRate - qualifyingValidators[0].commissionRate
}))
};
return strategy;
}
4. Performance Benchmarking
async function benchmarkValidatorPerformance() {
const validators = await analyzeValidatorRewards();
const benchmark = {
networkAverage: validators.reduce((sum, v) => sum + v.apy, 0) / validators.length,
topQuartile: validators.sort((a, b) => b.apy - a.apy)[Math.floor(validators.length * 0.25)].apy,
median: validators.sort((a, b) => b.apy - a.apy)[Math.floor(validators.length * 0.5)].apy,
bottomQuartile: validators.sort((a, b) => b.apy - a.apy)[Math.floor(validators.length * 0.75)].apy
};
// Categorize validators
const categorized = {
topPerformers: validators.filter(v => v.apy >= benchmark.topQuartile).length,
aboveAverage: validators.filter(v => v.apy > benchmark.networkAverage && v.apy < benchmark.topQuartile).length,
belowAverage: validators.filter(v => v.apy < benchmark.networkAverage && v.apy > benchmark.bottomQuartile).length,
underperformers: validators.filter(v => v.apy <= benchmark.bottomQuartile).length
};
return {
benchmark,
categorized,
analysis: {
competitionLevel: benchmark.topQuartile - benchmark.bottomQuartile,
networkHealth: benchmark.networkAverage > 0.04 ? 'Healthy' : 'Needs Attention',
diversityScore: (benchmark.topQuartile - benchmark.bottomQuartile) / benchmark.networkAverage
}
};
}
APY Considerations
Understanding APY Calculations
- Base Network Rewards: Fundamental rewards distributed to all validators
- Commission Impact: Higher commission reduces delegator APY
- Performance Factors: Uptime and participation affect actual returns
- Network Participation: Total staking ratio influences individual rewards
Risk Factors
function assessValidatorRisk(validator) {
const risks = [];
const score = { low: 0, medium: 0, high: 0 };
// Commission risk
if (validator.commissionRate > 0.1) {
risks.push('High commission rate');
score.high += 2;
} else if (validator.commissionRate > 0.05) {
risks.push('Moderate commission rate');
score.medium += 1;
} else {
score.low += 1;
}
// Pool size risk
if (validator.stakingPoolSuiBalance < 1000000000000) {
risks.push('Small staking pool');
score.high += 1;
} else if (validator.stakingPoolSuiBalance < 5000000000000) {
score.medium += 1;
} else {
score.low += 1;
}
// APY sustainability risk
if (validator.apy > 0.08) {
risks.push('Unusually high APY');
score.high += 1;
} else if (validator.apy < 0.03) {
risks.push('Below average APY');
score.medium += 1;
} else {
score.low += 1;
}
const totalScore = score.high * 3 + score.medium * 2 + score.low * 1;
const maxScore = 3 * 3; // 3 factors * max weight
const riskLevel = totalScore <= maxScore * 0.33 ? 'Low' :
totalScore <= maxScore * 0.66 ? 'Medium' : 'High';
return {
level: riskLevel,
factors: risks,
score: totalScore,
recommendation: riskLevel === 'Low' ? 'Recommended for conservative stakers' :
riskLevel === 'Medium' ? 'Suitable for balanced portfolios' :
'Only for risk-tolerant stakers'
};
}
Best Practices
1. Regular Monitoring
// Monitor APY changes to optimize staking positions
const monitoringStrategy = {
frequency: 'daily', // Check APY daily
threshold: 0.005, // 0.5% change triggers review
rebalanceThreshold: 0.01, // 1% difference triggers rebalancing
async checkAndRebalance(currentStaking) {
const currentValidators = await getValidatorsApy();
for (const stake of currentStaking) {
const currentAPY = currentValidators.find(v => v.address === stake.validator)?.apy;
const bestAPY = Math.max(...currentValidators.map(v => v.apy));
if (bestAPY - currentAPY >= this.rebalanceThreshold) {
console.log(`Consider rebalancing from ${stake.validator} (${currentAPY*100:.2f}%) to better option`);
}
}
}
};
2. Diversification Strategy
const diversificationRules = {
maxAllocationPerValidator: 0.4, // Max 40% in one validator
minValidators: 3, // Spread across at least 3 validators
commissionSpread: 0.03, // Vary commission rates by max 3%
validatePortfolio(portfolio) {
const totalAllocation = portfolio.reduce((sum, p) => sum + p.allocation, 0);
return {
diversified: portfolio.length >= this.minValidators,
concentrationRisk: portfolio.some(p => p.allocation / totalAllocation > this.maxAllocationPerValidator),
commissionVariance: Math.max(...portfolio.map(p => p.validator.commissionRate)) -
Math.min(...portfolio.map(p => p.validator.commissionRate))
};
}
};
3. Performance Tracking
// Track actual vs expected performance
class PerformanceTracker {
constructor() {
this.stakingHistory = [];
}
recordStaking(validatorAddress, amount, expectedAPY) {
this.stakingHistory.push({
validator: validatorAddress,
amount: amount,
expectedAPY: expectedAPY,
startDate: Date.now(),
actualReturns: []
});
}
updateReturns(validatorAddress, actualReturn, period) {
const staking = this.stakingHistory.find(s => s.validator === validatorAddress);
if (staking) {
staking.actualReturns.push({
period: period,
return: actualReturn,
date: Date.now()
});
}
}
calculatePerformanceMetrics(validatorAddress) {
const staking = this.stakingHistory.find(s => s.validator === validatorAddress);
if (!staking || staking.actualReturns.length === 0) return null;
const actualAPY = staking.actualReturns[staking.actualReturns.length - 1].return / staking.amount;
const variance = actualAPY - staking.expectedAPY;
return {
expectedAPY: staking.expectedAPY,
actualAPY: actualAPY,
variance: variance,
performance: variance > 0.005 ? 'Outperforming' :
variance < -0.005 ? 'Underperforming' : 'On Track'
};
}
}
Error Handling
async function robustGetValidatorsApy(retries = 3) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const apyData = await client.getValidatorsApy();
if (!apyData || apyData.length === 0) {
throw new Error('No APY data returned');
}
return { success: true, data: apyData };
} catch (error) {
console.warn(`APY fetch attempt ${attempt} failed:`, error.message);
if (attempt === retries) {
return {
success: false,
error: error.message,
fallback: 'Use cached data or default to manual validator selection'
};
}
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
}
Related Methods
- suix_getLatestSuiSystemState - Get detailed validator information
- suix_getStakes - Get staking positions
- suix_getBalance - Check available balance for staking
Notes
- APY values are estimates based on current network conditions and historical performance
- Commission rates affect the actual APY received by delegators
- Network rewards fluctuate based on overall network activity and participation
- Validator performance can impact actual returns compared to estimated APY
- Pool size and stability should be considered alongside APY for risk assessment
- APY calculations typically assume compound interest and consistent performance
Need help? Contact our support team or check the Sui documentation.