Skip to main content

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#

ParameterTypeRequiredDescription
epoch_iduint64NoSpecific epoch number (omit for current epoch)
read_maskFieldMaskNoFields to include in response

Field Mask Options#

PathDescription
epochEpoch number
epoch_start_timestampEpoch start time (milliseconds)
epoch_end_timestampEpoch end time (milliseconds)
validatorsActive validator set
epoch_total_transactionsTotal transactions in epoch
reference_gas_priceReference 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#

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);

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#

MetricValue
Typical Latency30-70ms
Response Size10-100KB (varies by validator count)
Cache RecommendedYes (1-5 minute TTL for current epoch)
Rate Limit ImpactLow

Common Errors#

Error CodeScenarioSolution
NOT_FOUNDEpoch doesn't exist yetQuery current or past epochs only
INVALID_ARGUMENTInvalid epoch IDVerify epoch number is valid
UNAUTHENTICATEDMissing/invalid tokenVerify x-api-key header

Need help? Contact support@dwellir.com or check the gRPC overview.