suix_getStakes
Returns the staking information for a given address, including delegated stakes and rewards on the Sui network.
Overview​
The suix_getStakes
method provides comprehensive staking information for any Sui address. This includes all active staking positions, delegation details, accumulated rewards, and staking history. The method is essential for tracking staking performance, managing validator delegations, and monitoring staking rewards across the Sui ecosystem.
Parameters​
Parameter | Type | Required | Description |
---|---|---|---|
owner | string | Yes | The Sui address to query staking information for |
Address Format​
- Must be a valid 66-character hex string with
0x
prefix - The address must exist on the Sui network
- Can query staking information for any address (not just owned)
Returns​
Returns an array of staking objects containing detailed staking information.
Field | Type | Description |
---|---|---|
stakingPoolId | string | The ID of the staking pool object |
validatorAddress | string | Address of the validator being delegated to |
status | string | Current status of the stake (Active, Pending, etc.) |
requestEpoch | string | Epoch when the staking request was made |
activeEpoch | string | Epoch when the stake became active |
principal | string | Original staking amount in MIST |
estimatedReward | string | Estimated rewards earned in MIST |
Staking Status Types​
- Active: Stake is currently active and earning rewards
- Pending: Stake is waiting to become active in the next epoch
- Unstaking: Stake is being withdrawn
- Withdrawn: Stake has been fully withdrawn
Code Examples​
- cURL
- JavaScript
- Python
# Get staking information for an address
curl -X POST https://sui-mainnet.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "suix_getStakes",
"params": [
"0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f"
],
"id": 1
}'
import { SuiClient } from '@mysten/sui.js/client';
const client = new SuiClient({
url: 'https://sui-mainnet.dwellir.com/YOUR_API_KEY'
});
// Get basic staking information
async function getStakingInfo(address) {
try {
const stakes = await client.getStakes({
owner: address
});
console.log(`Found ${stakes.length} staking positions`);
stakes.forEach((stake, index) => {
console.log(`\n${index + 1}. Staking Pool: ${stake.stakingPoolId}`);
console.log(` Validator: ${stake.validatorAddress}`);
console.log(` Status: ${stake.status}`);
console.log(` Principal: ${Number(stake.principal) / 1_000_000_000} SUI`);
console.log(` Estimated Reward: ${Number(stake.estimatedReward) / 1_000_000_000} SUI`);
console.log(` Active Since Epoch: ${stake.activeEpoch}`);
});
return stakes;
} catch (error) {
console.error('Failed to get staking information:', error);
return [];
}
}
// Staking portfolio analyzer
async function analyzeStakingPortfolio(address) {
const stakes = await client.getStakes({ owner: address });
if (stakes.length === 0) {
return {
address: address,
totalStakes: 0,
message: 'No active staking positions found'
};
}
const analysis = {
address: address,
totalStakes: stakes.length,
totalPrincipal: BigInt(0),
totalRewards: BigInt(0),
activeStakes: 0,
pendingStakes: 0,
validators: new Set(),
stakingPools: new Set(),
byStatus: {},
byValidator: {},
performance: {
totalROI: 0,
averageROI: 0,
bestPerforming: null,
worstPerforming: null
}
};
// Process each stake
stakes.forEach(stake => {
const principal = BigInt(stake.principal);
const reward = BigInt(stake.estimatedReward);
analysis.totalPrincipal += principal;
analysis.totalRewards += reward;
// Track status
if (stake.status === 'Active') {
analysis.activeStakes++;
} else if (stake.status === 'Pending') {
analysis.pendingStakes++;
}
analysis.byStatus[stake.status] = (analysis.byStatus[stake.status] || 0) + 1;
// Track validators and pools
analysis.validators.add(stake.validatorAddress);
analysis.stakingPools.add(stake.stakingPoolId);
// Group by validator
if (!analysis.byValidator[stake.validatorAddress]) {
analysis.byValidator[stake.validatorAddress] = {
stakes: 0,
totalPrincipal: BigInt(0),
totalRewards: BigInt(0),
stakingPools: new Set()
};
}
const validatorData = analysis.byValidator[stake.validatorAddress];
validatorData.stakes++;
validatorData.totalPrincipal += principal;
validatorData.totalRewards += reward;
validatorData.stakingPools.add(stake.stakingPoolId);
// Calculate individual ROI for performance tracking
const roi = principal > 0 ? Number(reward) / Number(principal) : 0;
if (!analysis.performance.bestPerforming || roi > analysis.performance.bestPerforming.roi) {
analysis.performance.bestPerforming = { ...stake, roi };
}
if (!analysis.performance.worstPerforming || roi < analysis.performance.worstPerforming.roi) {
analysis.performance.worstPerforming = { ...stake, roi };
}
});
// Calculate overall performance
analysis.performance.totalROI = analysis.totalPrincipal > 0 ?
Number(analysis.totalRewards) / Number(analysis.totalPrincipal) : 0;
analysis.performance.averageROI = analysis.performance.totalROI;
// Convert sets to counts
analysis.uniqueValidators = analysis.validators.size;
analysis.uniqueStakingPools = analysis.stakingPools.size;
// Convert BigInt to strings for JSON serialization
analysis.totalPrincipalSUI = Number(analysis.totalPrincipal) / 1_000_000_000;
analysis.totalRewardsSUI = Number(analysis.totalRewards) / 1_000_000_000;
return analysis;
}
// Staking rewards tracker
class StakingRewardsTracker {
constructor(client) {
this.client = client;
this.trackingData = new Map();
}
async trackRewards(addresses, trackingPeriod = 'daily') {
const results = {};
for (const address of addresses) {
const stakes = await this.client.getStakes({ owner: address });
const currentTime = Date.now();
const addressData = {
address: address,
lastUpdated: currentTime,
stakes: stakes,
summary: {
totalStaked: stakes.reduce((sum, stake) => sum + BigInt(stake.principal), BigInt(0)),
totalRewards: stakes.reduce((sum, stake) => sum + BigInt(stake.estimatedReward), BigInt(0)),
activeStakes: stakes.filter(s => s.status === 'Active').length
}
};
// Compare with previous data if available
const previousData = this.trackingData.get(address);
if (previousData) {
addressData.changes = this.calculateChanges(previousData, addressData);
}
// Store current data for future comparisons
this.trackingData.set(address, addressData);
results[address] = addressData;
}
return results;
}
calculateChanges(previous, current) {
const changes = {
stakesAdded: 0,
stakesRemoved: 0,
rewardsEarned: BigInt(0),
principalChanged: BigInt(0),
timeDelta: current.lastUpdated - previous.lastUpdated
};
// Create maps for easier comparison
const prevStakes = new Map(previous.stakes.map(s => [s.stakingPoolId, s]));
const currentStakes = new Map(current.stakes.map(s => [s.stakingPoolId, s]));
// Check for new stakes
currentStakes.forEach((stake, poolId) => {
if (!prevStakes.has(poolId)) {
changes.stakesAdded++;
changes.principalChanged += BigInt(stake.principal);
}
});
// Check for removed stakes and reward changes
prevStakes.forEach((prevStake, poolId) => {
if (!currentStakes.has(poolId)) {
changes.stakesRemoved++;
changes.principalChanged -= BigInt(prevStake.principal);
} else {
const currentStake = currentStakes.get(poolId);
const rewardDiff = BigInt(currentStake.estimatedReward) - BigInt(prevStake.estimatedReward);
changes.rewardsEarned += rewardDiff;
const principalDiff = BigInt(currentStake.principal) - BigInt(prevStake.principal);
changes.principalChanged += principalDiff;
}
});
return changes;
}
generateReport(trackingResults) {
const report = {
timestamp: Date.now(),
totalAddresses: Object.keys(trackingResults).length,
aggregatedStats: {
totalStaked: BigInt(0),
totalRewards: BigInt(0),
totalActiveStakes: 0
},
topPerformers: [],
recentChanges: []
};
const performanceData = [];
Object.values(trackingResults).forEach(addressData => {
const { address, summary, changes } = addressData;
report.aggregatedStats.totalStaked += summary.totalStaked;
report.aggregatedStats.totalRewards += summary.totalRewards;
report.aggregatedStats.totalActiveStakes += summary.activeStakes;
// Calculate performance metrics
const roi = summary.totalStaked > 0 ?
Number(summary.totalRewards) / Number(summary.totalStaked) : 0;
performanceData.push({
address: address,
roi: roi,
totalStaked: summary.totalStaked,
totalRewards: summary.totalRewards,
activeStakes: summary.activeStakes
});
// Track changes
if (changes) {
report.recentChanges.push({
address: address,
stakesAdded: changes.stakesAdded,
stakesRemoved: changes.stakesRemoved,
rewardsEarned: Number(changes.rewardsEarned) / 1_000_000_000,
principalChanged: Number(changes.principalChanged) / 1_000_000_000
});
}
});
// Sort by ROI and get top performers
report.topPerformers = performanceData
.sort((a, b) => b.roi - a.roi)
.slice(0, 10)
.map(p => ({
address: p.address,
roi: `${(p.roi * 100).toFixed(2)}%`,
totalStaked: `${Number(p.totalStaked) / 1_000_000_000} SUI`,
totalRewards: `${Number(p.totalRewards) / 1_000_000_000} SUI`,
activeStakes: p.activeStakes
}));
return report;
}
}
// Validator distribution analyzer
async function analyzeValidatorDistribution(address) {
const stakes = await client.getStakes({ owner: address });
if (stakes.length === 0) {
return { error: 'No staking positions found' };
}
// Group stakes by validator
const validatorDistribution = stakes.reduce((acc, stake) => {
const validator = stake.validatorAddress;
if (!acc[validator]) {
acc[validator] = {
stakes: [],
totalPrincipal: BigInt(0),
totalRewards: BigInt(0),
stakingPools: new Set(),
status: { Active: 0, Pending: 0, other: 0 }
};
}
const validatorData = acc[validator];
validatorData.stakes.push(stake);
validatorData.totalPrincipal += BigInt(stake.principal);
validatorData.totalRewards += BigInt(stake.estimatedReward);
validatorData.stakingPools.add(stake.stakingPoolId);
if (validatorData.status[stake.status]) {
validatorData.status[stake.status]++;
} else {
validatorData.status.other++;
}
return acc;
}, {});
// Calculate distribution metrics
const totalPrincipal = stakes.reduce((sum, stake) => sum + BigInt(stake.principal), BigInt(0));
const distribution = Object.entries(validatorDistribution).map(([validator, data]) => {
const percentage = totalPrincipal > 0 ?
Number(data.totalPrincipal) / Number(totalPrincipal) : 0;
return {
validatorAddress: validator,
stakes: data.stakes.length,
stakingPools: data.stakingPools.size,
principalAmount: Number(data.totalPrincipal) / 1_000_000_000,
rewardsAmount: Number(data.totalRewards) / 1_000_000_000,
percentage: percentage * 100,
roi: data.totalPrincipal > 0 ?
Number(data.totalRewards) / Number(data.totalPrincipal) : 0,
statusBreakdown: data.status
};
}).sort((a, b) => b.principalAmount - a.principalAmount);
return {
address: address,
totalValidators: distribution.length,
totalStakes: stakes.length,
totalPrincipal: Number(totalPrincipal) / 1_000_000_000,
distribution: distribution,
diversificationScore: distribution.length / stakes.length, // Higher is more diversified
concentrationRisk: Math.max(...distribution.map(d => d.percentage)) // Highest single allocation
};
}
// Staking optimization suggestions
class StakingOptimizer {
constructor(client) {
this.client = client;
}
async analyzeAndSuggest(address) {
const stakes = await this.client.getStakes({ owner: address });
const validatorAPYs = await this.client.getValidatorsApy();
if (stakes.length === 0) {
return {
address: address,
suggestions: ['Consider starting to stake SUI to earn rewards']
};
}
const analysis = await analyzeValidatorDistribution(address);
const suggestions = [];
// Check for concentration risk
if (analysis.concentrationRisk > 70) {
suggestions.push({
type: 'DIVERSIFICATION',
priority: 'HIGH',
message: `${analysis.concentrationRisk.toFixed(1)}% of stake is with one validator. Consider diversifying.`,
action: 'Redistribute stakes across multiple validators'
});
}
// Check for low-performing validators
const apyMap = validatorAPYs.reduce((acc, v) => {
acc[v.address] = v.apy;
return acc;
}, {});
const avgAPY = validatorAPYs.reduce((sum, v) => sum + v.apy, 0) / validatorAPYs.length;
analysis.distribution.forEach(dist => {
const validatorAPY = apyMap[dist.validatorAddress] || 0;
if (validatorAPY < avgAPY * 0.8 && dist.percentage > 10) { // 20% below average
suggestions.push({
type: 'PERFORMANCE',
priority: 'MEDIUM',
message: `Validator ${dist.validatorAddress.substring(0, 16)}... has below-average APY (${(validatorAPY * 100).toFixed(2)}%)`,
action: `Consider moving ${dist.principalAmount.toFixed(2)} SUI to higher-performing validators`
});
}
});
// Check for pending stakes
const pendingStakes = stakes.filter(s => s.status === 'Pending');
if (pendingStakes.length > 0) {
suggestions.push({
type: 'STATUS',
priority: 'LOW',
message: `${pendingStakes.length} stakes are still pending activation`,
action: 'These will become active in the next epoch'
});
}
// Check for small stakes that could be consolidated
const smallStakes = stakes.filter(s => Number(s.principal) / 1_000_000_000 < 10); // < 10 SUI
if (smallStakes.length > 2) {
suggestions.push({
type: 'CONSOLIDATION',
priority: 'LOW',
message: `${smallStakes.length} small stakes (< 10 SUI each) could be consolidated`,
action: 'Consider consolidating small stakes to reduce management overhead'
});
}
return {
address: address,
currentAnalysis: analysis,
suggestions: suggestions,
overallScore: this.calculateOptimizationScore(analysis, suggestions)
};
}
calculateOptimizationScore(analysis, suggestions) {
let score = 100;
// Penalize concentration risk
if (analysis.concentrationRisk > 70) score -= 30;
else if (analysis.concentrationRisk > 50) score -= 15;
// Penalize lack of diversification
if (analysis.diversificationScore < 0.3) score -= 20;
else if (analysis.diversificationScore < 0.5) score -= 10;
// Penalize high-priority suggestions
suggestions.forEach(suggestion => {
if (suggestion.priority === 'HIGH') score -= 15;
else if (suggestion.priority === 'MEDIUM') score -= 8;
else score -= 3;
});
return Math.max(0, Math.min(100, score));
}
}
// Usage examples
const address = '0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f';
// Basic staking info
const stakingInfo = await getStakingInfo(address);
console.log('Staking positions:', stakingInfo);
// Portfolio analysis
const portfolio = await analyzeStakingPortfolio(address);
console.log('\nPortfolio Analysis:');
console.log(`Total Stakes: ${portfolio.totalStakes}`);
console.log(`Total Staked: ${portfolio.totalPrincipalSUI} SUI`);
console.log(`Total Rewards: ${portfolio.totalRewardsSUI} SUI`);
console.log(`Overall ROI: ${(portfolio.performance.totalROI * 100).toFixed(2)}%`);
// Validator distribution
const distribution = await analyzeValidatorDistribution(address);
console.log('\nValidator Distribution:');
distribution.distribution.forEach((dist, index) => {
console.log(`${index + 1}. ${dist.validatorAddress.substring(0, 16)}...`);
console.log(` Stakes: ${dist.stakes}, Amount: ${dist.principalAmount.toFixed(2)} SUI (${dist.percentage.toFixed(1)}%)`);
console.log(` ROI: ${(dist.roi * 100).toFixed(2)}%`);
});
// Optimization suggestions
const optimizer = new StakingOptimizer(client);
const optimization = await optimizer.analyzeAndSuggest(address);
console.log('\nOptimization Analysis:');
console.log(`Overall Score: ${optimization.overallScore}/100`);
console.log('Suggestions:');
optimization.suggestions.forEach((suggestion, index) => {
console.log(`${index + 1}. [${suggestion.priority}] ${suggestion.message}`);
console.log(` Action: ${suggestion.action}`);
});
// Rewards tracking (example setup)
const tracker = new StakingRewardsTracker(client);
const trackingResults = await tracker.trackRewards([address]);
console.log('\nRewards Tracking:', trackingResults);
import requests
import json
from typing import Dict, List, Any, Optional
from decimal import Decimal
from dataclasses import dataclass
import time
@dataclass
class StakeInfo:
staking_pool_id: str
validator_address: str
status: str
request_epoch: str
active_epoch: str
principal: int
estimated_reward: int
class SuiStakingClient:
def __init__(self, rpc_url: str):
self.rpc_url = rpc_url
def get_stakes(self, owner: str) -> List[Dict[str, Any]]:
"""Get staking information for an address"""
payload = {
"jsonrpc": "2.0",
"method": "suix_getStakes",
"params": [owner],
"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 []
return result['result']
except Exception as e:
print(f"Error fetching stakes: {e}")
return []
def get_validators_apy(self) -> List[Dict[str, Any]]:
"""Get validator APY information"""
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 getting validator APY: {result['error']}")
return []
return result['result']
except Exception as e:
print(f"Error fetching validator APY: {e}")
return []
class StakingPortfolioAnalyzer:
def __init__(self, client: SuiStakingClient):
self.client = client
def analyze_portfolio(self, address: str) -> Dict[str, Any]:
"""Analyze staking portfolio for an address"""
stakes = self.client.get_stakes(address)
if not stakes:
return {
'address': address,
'total_stakes': 0,
'message': 'No active staking positions found'
}
analysis = {
'address': address,
'total_stakes': len(stakes),
'total_principal': 0,
'total_rewards': 0,
'active_stakes': 0,
'pending_stakes': 0,
'unique_validators': set(),
'unique_staking_pools': set(),
'by_status': {},
'by_validator': {},
'performance': {
'total_roi': 0,
'best_performing': None,
'worst_performing': None
}
}
roi_values = []
for stake in stakes:
principal = int(stake['principal'])
reward = int(stake['estimatedReward'])
analysis['total_principal'] += principal
analysis['total_rewards'] += reward
# Count by status
status = stake['status']
analysis['by_status'][status] = analysis['by_status'].get(status, 0) + 1
if status == 'Active':
analysis['active_stakes'] += 1
elif status == 'Pending':
analysis['pending_stakes'] += 1
# Track unique validators and pools
validator = stake['validatorAddress']
pool_id = stake['stakingPoolId']
analysis['unique_validators'].add(validator)
analysis['unique_staking_pools'].add(pool_id)
# Group by validator
if validator not in analysis['by_validator']:
analysis['by_validator'][validator] = {
'stakes': 0,
'total_principal': 0,
'total_rewards': 0,
'staking_pools': set()
}
validator_data = analysis['by_validator'][validator]
validator_data['stakes'] += 1
validator_data['total_principal'] += principal
validator_data['total_rewards'] += reward
validator_data['staking_pools'].add(pool_id)
# Calculate ROI
roi = reward / principal if principal > 0 else 0
roi_values.append(roi)
stake_with_roi = {**stake, 'roi': roi}
if (analysis['performance']['best_performing'] is None or
roi > analysis['performance']['best_performing']['roi']):
analysis['performance']['best_performing'] = stake_with_roi
if (analysis['performance']['worst_performing'] is None or
roi < analysis['performance']['worst_performing']['roi']):
analysis['performance']['worst_performing'] = stake_with_roi
# Calculate overall performance
analysis['performance']['total_roi'] = (
analysis['total_rewards'] / analysis['total_principal']
if analysis['total_principal'] > 0 else 0
)
# Convert to human readable
analysis['total_principal_sui'] = analysis['total_principal'] / 1_000_000_000
analysis['total_rewards_sui'] = analysis['total_rewards'] / 1_000_000_000
analysis['unique_validators_count'] = len(analysis['unique_validators'])
analysis['unique_staking_pools_count'] = len(analysis['unique_staking_pools'])
# Clean up sets for JSON serialization
del analysis['unique_validators']
del analysis['unique_staking_pools']
return analysis
def analyze_validator_distribution(self, address: str) -> Dict[str, Any]:
"""Analyze how stakes are distributed across validators"""
stakes = self.client.get_stakes(address)
if not stakes:
return {'error': 'No staking positions found'}
validator_distribution = {}
total_principal = 0
# Group stakes by validator
for stake in stakes:
validator = stake['validatorAddress']
principal = int(stake['principal'])
reward = int(stake['estimatedReward'])
total_principal += principal
if validator not in validator_distribution:
validator_distribution[validator] = {
'stakes': [],
'total_principal': 0,
'total_rewards': 0,
'staking_pools': set(),
'status_count': {'Active': 0, 'Pending': 0, 'other': 0}
}
validator_data = validator_distribution[validator]
validator_data['stakes'].append(stake)
validator_data['total_principal'] += principal
validator_data['total_rewards'] += reward
validator_data['staking_pools'].add(stake['stakingPoolId'])
status = stake['status']
if status in validator_data['status_count']:
validator_data['status_count'][status] += 1
else:
validator_data['status_count']['other'] += 1
# Calculate distribution metrics
distribution = []
for validator, data in validator_distribution.items():
percentage = (data['total_principal'] / total_principal * 100) if total_principal > 0 else 0
roi = (data['total_rewards'] / data['total_principal']) if data['total_principal'] > 0 else 0
distribution.append({
'validator_address': validator,
'stakes': len(data['stakes']),
'staking_pools': len(data['staking_pools']),
'principal_amount': data['total_principal'] / 1_000_000_000,
'rewards_amount': data['total_rewards'] / 1_000_000_000,
'percentage': percentage,
'roi': roi,
'status_breakdown': data['status_count']
})
# Sort by principal amount descending
distribution.sort(key=lambda x: x['principal_amount'], reverse=True)
# Calculate risk metrics
concentration_risk = max(d['percentage'] for d in distribution) if distribution else 0
diversification_score = len(distribution) / len(stakes) if stakes else 0
return {
'address': address,
'total_validators': len(distribution),
'total_stakes': len(stakes),
'total_principal': total_principal / 1_000_000_000,
'distribution': distribution,
'diversification_score': diversification_score,
'concentration_risk': concentration_risk
}
class StakingOptimizer:
def __init__(self, client: SuiStakingClient):
self.client = client
def analyze_and_suggest(self, address: str) -> Dict[str, Any]:
"""Analyze staking portfolio and provide optimization suggestions"""
stakes = self.client.get_stakes(address)
if not stakes:
return {
'address': address,
'suggestions': [
{
'type': 'GETTING_STARTED',
'priority': 'HIGH',
'message': 'No staking positions found',
'action': 'Consider starting to stake SUI to earn rewards'
}
]
}
analyzer = StakingPortfolioAnalyzer(self.client)
distribution = analyzer.analyze_validator_distribution(address)
suggestions = []
# Get validator APY data for performance analysis
try:
validator_apys = self.client.get_validators_apy()
apy_map = {v['address']: v['apy'] for v in validator_apys}
avg_apy = sum(v['apy'] for v in validator_apys) / len(validator_apys) if validator_apys else 0
except:
apy_map = {}
avg_apy = 0
# Check concentration risk
if distribution['concentration_risk'] > 70:
suggestions.append({
'type': 'DIVERSIFICATION',
'priority': 'HIGH',
'message': f"{distribution['concentration_risk']:.1f}% of stake is with one validator",
'action': 'Consider redistributing stakes across multiple validators to reduce risk'
})
elif distribution['concentration_risk'] > 50:
suggestions.append({
'type': 'DIVERSIFICATION',
'priority': 'MEDIUM',
'message': f"{distribution['concentration_risk']:.1f}% of stake is with one validator",
'action': 'Consider some diversification to reduce concentration risk'
})
# Check for low-performing validators
if apy_map and avg_apy > 0:
for dist in distribution['distribution']:
validator_apy = apy_map.get(dist['validator_address'], 0)
if validator_apy < avg_apy * 0.8 and dist['percentage'] > 10: # 20% below average
suggestions.append({
'type': 'PERFORMANCE',
'priority': 'MEDIUM',
'message': f"Validator {dist['validator_address'][:16]}... has below-average APY ({validator_apy*100:.2f}%)",
'action': f"Consider moving {dist['principal_amount']:.2f} SUI to higher-performing validators"
})
# Check for pending stakes
pending_stakes = [s for s in stakes if s['status'] == 'Pending']
if pending_stakes:
suggestions.append({
'type': 'STATUS',
'priority': 'LOW',
'message': f"{len(pending_stakes)} stakes are still pending activation",
'action': 'These will become active in the next epoch automatically'
})
# Check for small stakes
small_stakes = [s for s in stakes if int(s['principal']) / 1_000_000_000 < 10] # < 10 SUI
if len(small_stakes) > 2:
suggestions.append({
'type': 'CONSOLIDATION',
'priority': 'LOW',
'message': f"{len(small_stakes)} small stakes (< 10 SUI each) could be consolidated",
'action': 'Consider consolidating small stakes to reduce management complexity'
})
# Calculate optimization score
score = self._calculate_optimization_score(distribution, suggestions)
return {
'address': address,
'current_analysis': distribution,
'suggestions': suggestions,
'optimization_score': score
}
def _calculate_optimization_score(self, distribution: Dict[str, Any], suggestions: List[Dict[str, Any]]) -> int:
"""Calculate optimization score from 0-100"""
score = 100
# Penalize concentration risk
concentration_risk = distribution.get('concentration_risk', 0)
if concentration_risk > 70:
score -= 30
elif concentration_risk > 50:
score -= 15
# Penalize lack of diversification
diversification = distribution.get('diversification_score', 1)
if diversification < 0.3:
score -= 20
elif diversification < 0.5:
score -= 10
# Penalize suggestions by priority
for suggestion in suggestions:
if suggestion['priority'] == 'HIGH':
score -= 15
elif suggestion['priority'] == 'MEDIUM':
score -= 8
else:
score -= 3
return max(0, min(100, score))
class StakingRewardsTracker:
def __init__(self, client: SuiStakingClient):
self.client = client
self.tracking_history = {}
def track_rewards(self, addresses: List[str]) -> Dict[str, Any]:
"""Track rewards for multiple addresses"""
results = {}
current_time = time.time()
for address in addresses:
stakes = self.client.get_stakes(address)
address_data = {
'address': address,
'timestamp': current_time,
'stakes': stakes,
'summary': {
'total_staked': sum(int(s['principal']) for s in stakes),
'total_rewards': sum(int(s['estimatedReward']) for s in stakes),
'active_stakes': len([s for s in stakes if s['status'] == 'Active'])
}
}
# Compare with previous data if available
if address in self.tracking_history:
previous = self.tracking_history[address]
address_data['changes'] = self._calculate_changes(previous, address_data)
# Store for future comparisons
self.tracking_history[address] = address_data
results[address] = address_data
return results
def _calculate_changes(self, previous: Dict[str, Any], current: Dict[str, Any]) -> Dict[str, Any]:
"""Calculate changes between tracking periods"""
changes = {
'stakes_added': 0,
'stakes_removed': 0,
'rewards_earned': 0,
'principal_changed': 0,
'time_delta': current['timestamp'] - previous['timestamp']
}
# Create maps for comparison
prev_stakes = {s['stakingPoolId']: s for s in previous['stakes']}
current_stakes = {s['stakingPoolId']: s for s in current['stakes']}
# Check for new stakes
for pool_id, stake in current_stakes.items():
if pool_id not in prev_stakes:
changes['stakes_added'] += 1
changes['principal_changed'] += int(stake['principal'])
# Check for removed stakes and reward changes
for pool_id, prev_stake in prev_stakes.items():
if pool_id not in current_stakes:
changes['stakes_removed'] += 1
changes['principal_changed'] -= int(prev_stake['principal'])
else:
current_stake = current_stakes[pool_id]
reward_diff = int(current_stake['estimatedReward']) - int(prev_stake['estimatedReward'])
changes['rewards_earned'] += reward_diff
principal_diff = int(current_stake['principal']) - int(prev_stake['principal'])
changes['principal_changed'] += principal_diff
return changes
def generate_report(self, tracking_results: Dict[str, Any]) -> Dict[str, Any]:
"""Generate a comprehensive tracking report"""
report = {
'timestamp': time.time(),
'total_addresses': len(tracking_results),
'aggregated_stats': {
'total_staked': 0,
'total_rewards': 0,
'total_active_stakes': 0
},
'top_performers': [],
'recent_changes': []
}
performance_data = []
for address_data in tracking_results.values():
summary = address_data['summary']
report['aggregated_stats']['total_staked'] += summary['total_staked']
report['aggregated_stats']['total_rewards'] += summary['total_rewards']
report['aggregated_stats']['total_active_stakes'] += summary['active_stakes']
# Calculate performance metrics
roi = (summary['total_rewards'] / summary['total_staked']
if summary['total_staked'] > 0 else 0)
performance_data.append({
'address': address_data['address'],
'roi': roi,
'total_staked': summary['total_staked'],
'total_rewards': summary['total_rewards'],
'active_stakes': summary['active_stakes']
})
# Track changes
if 'changes' in address_data:
changes = address_data['changes']
report['recent_changes'].append({
'address': address_data['address'],
'stakes_added': changes['stakes_added'],
'stakes_removed': changes['stakes_removed'],
'rewards_earned': changes['rewards_earned'] / 1_000_000_000,
'principal_changed': changes['principal_changed'] / 1_000_000_000
})
# Get top performers
performance_data.sort(key=lambda x: x['roi'], reverse=True)
report['top_performers'] = [
{
'address': p['address'],
'roi': f"{p['roi'] * 100:.2f}%",
'total_staked': f"{p['total_staked'] / 1_000_000_000:.2f} SUI",
'total_rewards': f"{p['total_rewards'] / 1_000_000_000:.2f} SUI",
'active_stakes': p['active_stakes']
}
for p in performance_data[:10]
]
# Convert to human readable
stats = report['aggregated_stats']
stats['total_staked'] = f"{stats['total_staked'] / 1_000_000_000:.2f} SUI"
stats['total_rewards'] = f"{stats['total_rewards'] / 1_000_000_000:.2f} SUI"
return report
# Usage examples
client = SuiStakingClient('https://sui-mainnet.dwellir.com/YOUR_API_KEY')
# Example 1: Basic staking information
address = '0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f'
print("Fetching staking information...")
stakes = client.get_stakes(address)
if stakes:
print(f"Found {len(stakes)} staking positions:")
for i, stake in enumerate(stakes, 1):
principal_sui = int(stake['principal']) / 1_000_000_000
reward_sui = int(stake['estimatedReward']) / 1_000_000_000
roi = (reward_sui / principal_sui * 100) if principal_sui > 0 else 0
print(f"\n{i}. Validator: {stake['validatorAddress'][:16]}...")
print(f" Status: {stake['status']}")
print(f" Principal: {principal_sui:.2f} SUI")
print(f" Estimated Reward: {reward_sui:.4f} SUI")
print(f" ROI: {roi:.2f}%")
print(f" Active Since Epoch: {stake['activeEpoch']}")
# Example 2: Portfolio analysis
print("\nAnalyzing staking portfolio...")
analyzer = StakingPortfolioAnalyzer(client)
portfolio = analyzer.analyze_portfolio(address)
if 'message' not in portfolio:
print(f"Portfolio Summary:")
print(f" Total Stakes: {portfolio['total_stakes']}")
print(f" Active Stakes: {portfolio['active_stakes']}")
print(f" Total Staked: {portfolio['total_principal_sui']:.2f} SUI")
print(f" Total Rewards: {portfolio['total_rewards_sui']:.4f} SUI")
print(f" Overall ROI: {portfolio['performance']['total_roi'] * 100:.2f}%")
print(f" Unique Validators: {portfolio['unique_validators_count']}")
# Example 3: Validator distribution analysis
print("\nAnalyzing validator distribution...")
distribution = analyzer.analyze_validator_distribution(address)
if 'error' not in distribution:
print(f"Distribution Analysis:")
print(f" Diversification Score: {distribution['diversification_score']:.2f}")
print(f" Concentration Risk: {distribution['concentration_risk']:.1f}%")
print(f"\nTop validators by stake:")
for i, dist in enumerate(distribution['distribution'][:5], 1):
print(f" {i}. {dist['validator_address'][:16]}...")
print(f" Amount: {dist['principal_amount']:.2f} SUI ({dist['percentage']:.1f}%)")
print(f" Stakes: {dist['stakes']}, ROI: {dist['roi'] * 100:.2f}%")
# Example 4: Optimization suggestions
print("\nGenerating optimization suggestions...")
optimizer = StakingOptimizer(client)
optimization = optimizer.analyze_and_suggest(address)
print(f"Optimization Score: {optimization['optimization_score']}/100")
if optimization['suggestions']:
print("Suggestions:")
for i, suggestion in enumerate(optimization['suggestions'], 1):
print(f" {i}. [{suggestion['priority']}] {suggestion['message']}")
print(f" Action: {suggestion['action']}")
else:
print("No optimization suggestions - portfolio looks good!")
# Example 5: Rewards tracking
print("\nSetting up rewards tracking...")
tracker = StakingRewardsTracker(client)
# Track rewards for multiple addresses
addresses_to_track = [address]
tracking_results = tracker.track_rewards(addresses_to_track)
print("Tracking Results:")
for addr, data in tracking_results.items():
summary = data['summary']
print(f" {addr[:16]}...")
print(f" Total Staked: {summary['total_staked'] / 1_000_000_000:.2f} SUI")
print(f" Total Rewards: {summary['total_rewards'] / 1_000_000_000:.4f} SUI")
print(f" Active Stakes: {summary['active_stakes']}")
if 'changes' in data:
changes = data['changes']
print(f" Recent Changes:")
print(f" Stakes Added: {changes['stakes_added']}")
print(f" Rewards Earned: {changes['rewards_earned'] / 1_000_000_000:.4f} SUI")
# Generate comprehensive report
report = tracker.generate_report(tracking_results)
print(f"\nAggregated Statistics:")
print(f" Total Addresses Tracked: {report['total_addresses']}")
print(f" Combined Staked: {report['aggregated_stats']['total_staked']}")
print(f" Combined Rewards: {report['aggregated_stats']['total_rewards']}")
print(f" Total Active Stakes: {report['aggregated_stats']['total_active_stakes']}")
Response Example​
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"stakingPoolId": "0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"validatorAddress": "0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f",
"status": "Active",
"requestEpoch": "245",
"activeEpoch": "246",
"principal": "5000000000000",
"estimatedReward": "250000000000"
},
{
"stakingPoolId": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"validatorAddress": "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba",
"status": "Pending",
"requestEpoch": "251",
"activeEpoch": "252",
"principal": "2000000000000",
"estimatedReward": "0"
}
]
}
Common Use Cases​
Staking Dashboard​
class StakingDashboard {
constructor(client) {
this.client = client;
}
async getDashboardData(address) {
const [stakes, validatorAPYs] = await Promise.all([
this.client.getStakes({ owner: address }),
this.client.getValidatorsApy()
]);
const apyMap = validatorAPYs.reduce((acc, v) => {
acc[v.address] = v.apy;
return acc;
}, {});
const dashboard = {
address: address,
totalStakes: stakes.length,
totalPrincipal: 0,
totalRewards: 0,
activeStakes: 0,
pendingStakes: 0,
estimatedYearlyRewards: 0,
validatorPerformance: [],
riskMetrics: {
diversificationScore: 0,
concentrationRisk: 0
}
};
const validatorGroups = {};
stakes.forEach(stake => {
const principal = BigInt(stake.principal);
const reward = BigInt(stake.estimatedReward);
const validatorAPY = apyMap[stake.validatorAddress] || 0;
dashboard.totalPrincipal += Number(principal);
dashboard.totalRewards += Number(reward);
if (stake.status === 'Active') {
dashboard.activeStakes++;
dashboard.estimatedYearlyRewards += Number(principal) * validatorAPY;
} else if (stake.status === 'Pending') {
dashboard.pendingStakes++;
}
// Group by validator
if (!validatorGroups[stake.validatorAddress]) {
validatorGroups[stake.validatorAddress] = {
validator: stake.validatorAddress,
stakes: 0,
totalPrincipal: 0,
totalRewards: 0,
apy: validatorAPY,
status: { Active: 0, Pending: 0, other: 0 }
};
}
const group = validatorGroups[stake.validatorAddress];
group.stakes++;
group.totalPrincipal += Number(principal);
group.totalRewards += Number(reward);
group.status[stake.status] = (group.status[stake.status] || 0) + 1;
});
dashboard.validatorPerformance = Object.values(validatorGroups)
.sort((a, b) => b.totalPrincipal - a.totalPrincipal);
// Calculate risk metrics
const validatorCount = Object.keys(validatorGroups).length;
dashboard.riskMetrics.diversificationScore = validatorCount / stakes.length;
if (dashboard.totalPrincipal > 0) {
const maxValidatorAllocation = Math.max(
...dashboard.validatorPerformance.map(v => v.totalPrincipal)
);
dashboard.riskMetrics.concentrationRisk =
(maxValidatorAllocation / dashboard.totalPrincipal) * 100;
}
// Convert to SUI
dashboard.totalPrincipalSUI = dashboard.totalPrincipal / 1_000_000_000;
dashboard.totalRewardsSUI = dashboard.totalRewards / 1_000_000_000;
dashboard.estimatedYearlyRewardsSUI = dashboard.estimatedYearlyRewards / 1_000_000_000;
return dashboard;
}
}
Staking Rewards Calculator​
class StakingRewardsCalculator {
constructor(client) {
this.client = client;
}
async calculateProjectedRewards(address, projectionPeriods = [30, 90, 365]) {
const stakes = await this.client.getStakes({ owner: address });
const validatorAPYs = await this.client.getValidatorsApy();
const apyMap = validatorAPYs.reduce((acc, v) => {
acc[v.address] = v.apy;
return acc;
}, {});
const projections = {
address: address,
currentStakes: stakes.length,
projectedRewards: [],
byValidator: {}
};
let totalActiveStake = 0;
stakes.forEach(stake => {
if (stake.status === 'Active') {
const principal = Number(stake.principal);
const validatorAPY = apyMap[stake.validatorAddress] || 0;
totalActiveStake += principal;
if (!projections.byValidator[stake.validatorAddress]) {
projections.byValidator[stake.validatorAddress] = {
totalStake: 0,
apy: validatorAPY,
projectedRewards: []
};
}
projections.byValidator[stake.validatorAddress].totalStake += principal;
}
});
// Calculate projections for each period
projectionPeriods.forEach(days => {
let totalProjectedReward = 0;
Object.entries(projections.byValidator).forEach(([validator, data]) => {
const dailyRate = data.apy / 365;
const compoundedReturn = data.totalStake * Math.pow(1 + dailyRate, days);
const projectedReward = compoundedReturn - data.totalStake;
data.projectedRewards.push({
days: days,
projectedReward: projectedReward,
totalValue: compoundedReturn
});
totalProjectedReward += projectedReward;
});
projections.projectedRewards.push({
days: days,
period: this.formatPeriod(days),
totalProjectedReward: totalProjectedReward,
totalProjectedRewardSUI: totalProjectedReward / 1_000_000_000,
effectiveAPY: Math.pow(
(totalActiveStake + totalProjectedReward) / totalActiveStake,
365 / days
) - 1
});
});
projections.currentStakeSUI = totalActiveStake / 1_000_000_000;
return projections;
}
formatPeriod(days) {
if (days < 30) return `${days} days`;
if (days < 365) return `${Math.round(days / 30)} months`;
return `${Math.round(days / 365 * 10) / 10} years`;
}
}
Error Handling​
async function safeGetStakes(address, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const stakes = await client.getStakes({ owner: address });
if (!Array.isArray(stakes)) {
throw new Error('Invalid response format - expected array');
}
return { success: true, data: stakes };
} catch (error) {
console.warn(`Stakes query attempt ${attempt} failed:`, error.message);
if (error.message.includes('Invalid address')) {
return {
success: false,
error: 'INVALID_ADDRESS',
message: 'Invalid Sui address format'
};
}
if (error.message.includes('Address not found')) {
return {
success: false,
error: 'ADDRESS_NOT_FOUND',
message: 'Address has no staking history'
};
}
if (attempt === maxRetries) {
return {
success: false,
error: 'MAX_RETRIES_EXCEEDED',
message: error.message
};
}
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
Best Practices​
- Regular Monitoring: Check staking positions regularly to optimize performance
- Diversification: Spread stakes across multiple validators to reduce risk
- Performance Analysis: Compare validator APYs and adjust allocations accordingly
- Risk Management: Monitor concentration risk and maintain appropriate diversification
- Epoch Awareness: Understand that staking changes take effect at epoch boundaries
- Reward Tracking: Keep track of accumulated rewards for tax and performance analysis
Related Methods​
- suix_getValidatorsApy - Get validator APY information
- suix_getLatestSuiSystemState - Get system state and validator details
- suix_getBalance - Check available balance for staking
Need help? Contact our support team or check the Sui documentation.