Docs

GetEpoch - Query Epoch Information

Retrieve Sui epoch information via gRPC including validators, stake distribution, and timing. Essential for staking applications and network monitoring with Dwellir.

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.

Overview

Sui operates on an epoch-based consensus model where the network progresses through discrete time periods (epochs), each lasting approximately 24 hours. At each epoch boundary, the validator set is reconfigured, staking rewards are distributed, and gas price references are updated. The GetEpoch method provides full visibility into this lifecycle, making it indispensable for staking dashboards, validator analytics, and governance tooling.

Unlike traditional blockchains where validator sets can change at any block, Sui's epoch model provides predictable validator rotation windows. This means applications can cache validator data for the duration of an epoch and only refresh at epoch boundaries, reducing query overhead significantly.

Key Capabilities

  • Epoch Timing: Determine when the current epoch started, when it ends, and how much time remains
  • Validator Set: Retrieve the full list of active validators including their voting power and metadata
  • Gas Price Reference: Access the reference gas price set for the epoch by validator consensus
  • Transaction Volume: Query total transactions processed within a given epoch
  • Historical Analysis: Fetch data for any past epoch to build trend analytics

Method Signature

Service: sui.rpc.v2.LedgerService Method: GetEpoch Type: Unary RPC

Use Cases

Epoch Timing Calculator

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
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.