GetEpoch
Query Epoch Information and Validator Details#
The GetEpoch method retrieves comprehensive information about a specific Sui epoch, including validator set, stake distribution, epoch timing, and network parameters. This is essential for staking applications, validator monitoring, and understanding network state transitions.
Method Signature#
Service: sui.rpc.v2beta2.LedgerService
Method: GetEpoch
Type: Unary RPC
Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
epoch_id | uint64 | No | Specific epoch number (omit for current epoch) |
read_mask | FieldMask | No | Fields to include in response |
Field Mask Options#
| Path | Description |
|---|---|
epoch | Epoch number |
epoch_start_timestamp | Epoch start time (milliseconds) |
epoch_end_timestamp | Epoch end time (milliseconds) |
validators | Active validator set |
epoch_total_transactions | Total transactions in epoch |
reference_gas_price | Reference gas price for epoch |
Response Structure#
message EpochInfo {
uint64 epoch = 1;
uint64 epoch_start_timestamp = 2;
uint64 epoch_end_timestamp = 3;
repeated ValidatorInfo validators = 4;
uint64 epoch_total_transactions = 5;
uint64 reference_gas_price = 6;
uint64 epoch_commitments_count = 7;
}
message ValidatorInfo {
string sui_address = 1;
string protocol_pubkey = 2;
string network_pubkey = 3;
uint64 voting_power = 4;
string name = 5;
string description = 6;
string image_url = 7;
string project_url = 8;
}
Code Examples#
- TypeScript
- Python
- Go
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
const ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com';
const API_TOKEN = 'your_api_token_here';
const packageDefinition = protoLoader.loadSync('./protos/ledger.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: ['./protos']
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any;
const credentials = grpc.credentials.createSsl();
const client = new protoDescriptor.sui.rpc.v2beta2.LedgerService(ENDPOINT, credentials);
const metadata = new grpc.Metadata();
metadata.add('x-api-key', API_TOKEN);
async function getEpoch(epochId?: number): Promise<any> {
return new Promise((resolve, reject) => {
const request: any = {};
if (epochId !== undefined) {
request.epoch_id = epochId;
}
client.GetEpoch(request, metadata, (error: any, response: any) => {
if (error) {
console.error('GetEpoch error:', error.message);
reject(error);
return;
}
resolve(response);
});
});
}
// Usage - Get current epoch
const currentEpoch = await getEpoch();
console.log('Epoch:', currentEpoch.epoch);
console.log('Start:', new Date(parseInt(currentEpoch.epoch_start_timestamp)));
console.log('End:', new Date(parseInt(currentEpoch.epoch_end_timestamp)));
console.log('Validators:', currentEpoch.validators.length);
console.log('Total Transactions:', currentEpoch.epoch_total_transactions);
console.log('Reference Gas Price:', currentEpoch.reference_gas_price);
// Get specific epoch
const epoch100 = await getEpoch(100);
console.log('Historical epoch data:', epoch100);
import grpc
import ledger_service_pb2
import ledger_service_pb2_grpc
from datetime import datetime
ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com'
API_TOKEN = 'your_api_token_here'
def get_epoch(epoch_id=None):
credentials = grpc.ssl_channel_credentials()
channel = grpc.secure_channel(ENDPOINT, credentials)
client = ledger_service_pb2_grpc.LedgerServiceStub(channel)
request = ledger_service_pb2.GetEpochRequest()
if epoch_id is not None:
request.epoch_id = epoch_id
metadata = [('x-api-key', API_TOKEN)]
response = client.GetEpoch(request, metadata=metadata)
print(f'Epoch: {response.epoch}')
print(f'Validators: {len(response.validators)}')
print(f'Transactions: {response.epoch_total_transactions}')
print(f'Gas Price: {response.reference_gas_price}')
channel.close()
return response
# Usage
current_epoch = get_epoch()
historical_epoch = get_epoch(100)
package main
import (
"context"
"fmt"
"log"
"time"
"sui-grpc-client/config"
pb "sui-grpc-client/sui/rpc/v2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/types/known/fieldmaskpb"
)
func main() {
// Load configuration from .env file
cfg, err := config.Load()
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
// Create TLS credentials
creds := credentials.NewClientTLSFromCert(nil, "")
// Connect to Dwellir
conn, err := grpc.NewClient(
cfg.Endpoint,
grpc.WithTransportCredentials(creds),
)
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
// Create ledger service client
client := pb.NewLedgerServiceClient(conn)
// Add authentication
ctx := metadata.AppendToOutgoingContext(
context.Background(),
"x-api-key", cfg.APIKey,
)
// Set timeout
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
// Get current epoch (no epoch parameter = current epoch)
request := &pb.GetEpochRequest{
ReadMask: &fieldmaskpb.FieldMask{
Paths: []string{
"epoch",
"first_checkpoint",
"last_checkpoint",
"start",
"end",
"reference_gas_price",
},
},
}
response, err := client.GetEpoch(ctx, request)
if err != nil {
log.Fatalf("Failed to get epoch: %v", err)
}
epoch := response.GetEpoch()
if epoch == nil {
log.Fatal("No epoch data returned")
}
// Display epoch information
fmt.Println("Current Epoch Information:")
fmt.Println("==========================")
fmt.Printf("Epoch: %d\n", epoch.GetEpoch())
fmt.Printf("First Checkpoint: %d\n", epoch.GetFirstCheckpoint())
fmt.Printf("Last Checkpoint: %d\n", epoch.GetLastCheckpoint())
if ts := epoch.GetStart(); ts != nil {
startTime := time.Unix(ts.GetSeconds(), int64(ts.GetNanos()))
fmt.Printf("Start Time: %s\n", startTime.Format(time.RFC3339))
}
if ts := epoch.GetEnd(); ts != nil {
endTime := time.Unix(ts.GetSeconds(), int64(ts.GetNanos()))
fmt.Printf("End Time: %s\n", endTime.Format(time.RFC3339))
}
fmt.Printf("Reference Gas Price: %d MIST\n", epoch.GetReferenceGasPrice())
}
Use Cases#
Epoch Timing Calculator#
interface EpochTiming {
epochNumber: number;
startTime: Date;
endTime: Date;
duration: number;
timeRemaining: number;
percentComplete: number;
}
async function getEpochTiming(): Promise<EpochTiming> {
const epoch = await getEpoch();
const startTime = new Date(parseInt(epoch.epoch_start_timestamp));
const endTime = new Date(parseInt(epoch.epoch_end_timestamp));
const now = new Date();
const duration = endTime.getTime() - startTime.getTime();
const elapsed = now.getTime() - startTime.getTime();
const timeRemaining = endTime.getTime() - now.getTime();
const percentComplete = (elapsed / duration) * 100;
return {
epochNumber: parseInt(epoch.epoch),
startTime,
endTime,
duration,
timeRemaining,
percentComplete
};
}
// Usage
const timing = await getEpochTiming();
console.log(`Epoch ${timing.epochNumber}`);
console.log(`Progress: ${timing.percentComplete.toFixed(2)}%`);
console.log(`Time remaining: ${Math.floor(timing.timeRemaining / 3600000)} hours`);
Validator Analysis#
interface ValidatorStats {
totalValidators: number;
totalVotingPower: bigint;
topValidators: Array<{
address: string;
name: string;
votingPower: bigint;
votingPowerPercent: number;
}>;
averageVotingPower: number;
}
async function analyzeValidators(epochId?: number): Promise<ValidatorStats> {
const epoch = await getEpoch(epochId);
const validators = epoch.validators;
const totalVotingPower = validators.reduce(
(sum: bigint, v: any) => sum + BigInt(v.voting_power),
0n
);
const validatorsWithPercent = validators.map((v: any) => {
const power = BigInt(v.voting_power);
const percent = (Number(power) / Number(totalVotingPower)) * 100;
return {
address: v.sui_address,
name: v.name,
votingPower: power,
votingPowerPercent: percent
};
});
// Sort by voting power
validatorsWithPercent.sort((a, b) =>
b.votingPower > a.votingPower ? 1 : -1
);
const topValidators = validatorsWithPercent.slice(0, 10);
return {
totalValidators: validators.length,
totalVotingPower,
topValidators,
averageVotingPower: Number(totalVotingPower) / validators.length
};
}
// Usage
const stats = await analyzeValidators();
console.log(`Total validators: ${stats.totalValidators}`);
console.log(`Total voting power: ${stats.totalVotingPower}`);
console.log('\nTop 10 validators:');
stats.topValidators.forEach((v, i) => {
console.log(`${i + 1}. ${v.name}: ${v.votingPowerPercent.toFixed(2)}%`);
});
Find Validator by Address#
async function findValidator(
validatorAddress: string,
epochId?: number
): Promise<any | null> {
const epoch = await getEpoch(epochId);
const validator = epoch.validators.find(
(v: any) => v.sui_address === validatorAddress
);
if (!validator) {
console.warn(`Validator ${validatorAddress} not found in epoch ${epoch.epoch}`);
return null;
}
return {
address: validator.sui_address,
name: validator.name,
description: validator.description,
votingPower: BigInt(validator.voting_power),
imageUrl: validator.image_url,
projectUrl: validator.project_url
};
}
// Usage
const validator = await findValidator('0x...');
if (validator) {
console.log('Validator:', validator.name);
console.log('Voting power:', validator.votingPower);
console.log('Website:', validator.projectUrl);
}
Epoch History Tracker#
interface EpochComparison {
epochNumber: number;
transactions: number;
gasPrice: bigint;
validatorCount: number;
duration: number;
}
async function compareEpochs(
startEpoch: number,
endEpoch: number
): Promise<EpochComparison[]> {
const epochPromises: Promise<any>[] = [];
for (let i = startEpoch; i <= endEpoch; i++) {
epochPromises.push(getEpoch(i));
}
const epochs = await Promise.all(epochPromises);
return epochs.map(epoch => {
const duration =
parseInt(epoch.epoch_end_timestamp) -
parseInt(epoch.epoch_start_timestamp);
return {
epochNumber: parseInt(epoch.epoch),
transactions: parseInt(epoch.epoch_total_transactions),
gasPrice: BigInt(epoch.reference_gas_price),
validatorCount: epoch.validators.length,
duration
};
});
}
// Usage - Compare last 10 epochs
const currentEpoch = await getEpoch();
const currentEpochNumber = parseInt(currentEpoch.epoch);
const history = await compareEpochs(
currentEpochNumber - 9,
currentEpochNumber
);
console.log('Epoch History:');
history.forEach(epoch => {
console.log(`Epoch ${epoch.epochNumber}:`);
console.log(` Transactions: ${epoch.transactions}`);
console.log(` Gas Price: ${epoch.gasPrice}`);
console.log(` Validators: ${epoch.validatorCount}`);
});
Staking Dashboard Data#
interface StakingDashboard {
currentEpoch: number;
epochProgress: number;
timeUntilNextEpoch: number;
referenceGasPrice: bigint;
totalValidators: number;
totalStake: bigint;
networkActivity: {
totalTransactions: number;
transactionsPerSecond: number;
};
}
async function getStakingDashboard(): Promise<StakingDashboard> {
const epoch = await getEpoch();
const startMs = parseInt(epoch.epoch_start_timestamp);
const endMs = parseInt(epoch.epoch_end_timestamp);
const nowMs = Date.now();
const duration = endMs - startMs;
const elapsed = nowMs - startMs;
const epochProgress = (elapsed / duration) * 100;
const timeUntilNextEpoch = endMs - nowMs;
const totalStake = epoch.validators.reduce(
(sum: bigint, v: any) => sum + BigInt(v.voting_power),
0n
);
const transactionsPerSecond =
parseInt(epoch.epoch_total_transactions) / (elapsed / 1000);
return {
currentEpoch: parseInt(epoch.epoch),
epochProgress,
timeUntilNextEpoch,
referenceGasPrice: BigInt(epoch.reference_gas_price),
totalValidators: epoch.validators.length,
totalStake,
networkActivity: {
totalTransactions: parseInt(epoch.epoch_total_transactions),
transactionsPerSecond
}
};
}
// Usage
const dashboard = await getStakingDashboard();
console.log('Sui Staking Dashboard');
console.log('====================');
console.log(`Current Epoch: ${dashboard.currentEpoch}`);
console.log(`Progress: ${dashboard.epochProgress.toFixed(2)}%`);
console.log(`Next epoch in: ${Math.floor(dashboard.timeUntilNextEpoch / 3600000)} hours`);
console.log(`Active Validators: ${dashboard.totalValidators}`);
console.log(`Total Stake: ${dashboard.totalStake}`);
console.log(`Network TPS: ${dashboard.networkActivity.transactionsPerSecond.toFixed(2)}`);
Best Practices#
Cache Current Epoch with Short TTL#
class EpochCache {
private currentEpoch: { data: any; timestamp: number } | null = null;
private ttl = 60000; // 1 minute
async getCurrentEpoch(): Promise<any> {
if (
this.currentEpoch &&
Date.now() - this.currentEpoch.timestamp < this.ttl
) {
return this.currentEpoch.data;
}
const epoch = await getEpoch();
this.currentEpoch = {
data: epoch,
timestamp: Date.now()
};
return epoch;
}
invalidate(): void {
this.currentEpoch = null;
}
}
Handle Epoch Transitions#
async function waitForNextEpoch(): Promise<number> {
const currentEpoch = await getEpoch();
const currentEpochNumber = parseInt(currentEpoch.epoch);
const epochEndTime = parseInt(currentEpoch.epoch_end_timestamp);
const timeUntilNext = epochEndTime - Date.now();
if (timeUntilNext > 0) {
console.log(`Waiting ${Math.floor(timeUntilNext / 1000)}s for next epoch`);
await new Promise(resolve => setTimeout(resolve, timeUntilNext + 5000));
}
const nextEpoch = await getEpoch();
return parseInt(nextEpoch.epoch);
}
Performance Characteristics#
| Metric | Value |
|---|---|
| Typical Latency | 30-70ms |
| Response Size | 10-100KB (varies by validator count) |
| Cache Recommended | Yes (1-5 minute TTL for current epoch) |
| Rate Limit Impact | Low |
Common Errors#
| Error Code | Scenario | Solution |
|---|---|---|
NOT_FOUND | Epoch doesn't exist yet | Query current or past epochs only |
INVALID_ARGUMENT | Invalid epoch ID | Verify epoch number is valid |
UNAUTHENTICATED | Missing/invalid token | Verify x-api-key header |
Related Methods#
- GetCheckpoint - Query checkpoint data
- SubscribeCheckpoints - Monitor new checkpoints
Need help? Contact support@dwellir.com or check the gRPC overview.