Skip to main content

Overview

The Distribution precompile provides access to Sei’s staking reward distribution system, allowing you to claim delegation rewards, set withdrawal addresses, and query pending rewards directly from EVM contracts. This bridges EVM functionality with Cosmos SDK distribution module. Contract Address: 0x0000000000000000000000000000000000001007

Key Features

  • Reward Claiming: Withdraw staking rewards from validator delegations
  • Batch Operations: Claim rewards from multiple validators in a single transaction
  • Reward Queries: Check pending rewards before claiming
  • Withdrawal Management: Set custom withdrawal addresses for rewards

Available Functions

State-Changing Functions

Withdraw staking rewards from a specific validator.Parameters:
  • validator (string): The validator’s operator address
Returns: Success boolean
const success = await distributionContract.withdrawDelegationRewards(
  "seivaloper1..."
);
Withdraw staking rewards from multiple validators in a single transaction.Parameters:
  • validators (string[]): Array of validator operator addresses
Returns: Success boolean
const validators = [
  "seivaloper1...",
  "seivaloper2...",
  "seivaloper3..."
];

const success = await distributionContract.withdrawMultipleDelegationRewards(
  validators
);
Set a custom address to receive withdrawn rewards.Parameters:
  • withdrawAddr (address): The EVM address to receive rewards
Returns: Success boolean
const success = await distributionContract.setWithdrawAddress(
  "0x742d35Cc6634C0532925a3b8D6Ac0c2fb15d8d2A"
);

View Functions

Query pending staking rewards for a delegator.Parameters:
  • delegatorAddress (address): The delegator’s EVM address
Returns: Rewards struct with detailed breakdownReturn Structure:
interface Rewards {
  rewards: Array<{
    coins: Array<{
      amount: bigint;
      decimals: bigint;
      denom: string;
    }>;
    validator_address: string;
  }>;
  total: Array<{
    amount: bigint;
    decimals: bigint;
    denom: string;
  }>;
}
const rewards = await distributionContract.rewards(
  "0x742d35Cc6634C0532925a3b8D6Ac0c2fb15d8d2A"
);

// Access total rewards
console.log('Total SEI rewards:', formatEther(rewards.total[0].amount));

// Access per-validator rewards
rewards.rewards.forEach(reward => {
  console.log(`Validator ${reward.validator_address}:`);
  reward.coins.forEach(coin => {
    console.log(`  ${coin.denom}: ${formatEther(coin.amount)}`);
  });
});

Usage Examples

  • Viem
  • Ethers.js
  • Manual
import { createPublicClient, createWalletClient, http, formatEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { 
  seiTestnet,
  DISTRIBUTION_PRECOMPILE_ABI,
  DISTRIBUTION_PRECOMPILE_ADDRESS 
} from '@sei-js/precompiles/viem';

const publicClient = createPublicClient({
  chain: seiTestnet,
  transport: http()
});

const account = privateKeyToAccount('0x...');
const walletClient = createWalletClient({
  account,
  chain: seiTestnet,
  transport: http()
});

// Query pending rewards
async function getPendingRewards(delegatorAddress: string) {
  const rewards = await publicClient.readContract({
    address: DISTRIBUTION_PRECOMPILE_ADDRESS,
    abi: DISTRIBUTION_PRECOMPILE_ABI,
    functionName: 'rewards',
    args: [delegatorAddress]
  });
  
  return {
    totalRewards: rewards.total.map(coin => ({
      denom: coin.denom,
      amount: formatEther(coin.amount),
      decimals: Number(coin.decimals)
    })),
    validatorRewards: rewards.rewards.map(reward => ({
      validator: reward.validator_address,
      coins: reward.coins.map(coin => ({
        denom: coin.denom,
        amount: formatEther(coin.amount),
        decimals: Number(coin.decimals)
      }))
    }))
  };
}

// Claim rewards from a single validator
async function claimRewardsFromValidator(validatorAddress: string) {
  const hash = await walletClient.writeContract({
    address: DISTRIBUTION_PRECOMPILE_ADDRESS,
    abi: DISTRIBUTION_PRECOMPILE_ABI,
    functionName: 'withdrawDelegationRewards',
    args: [validatorAddress]
  });
  
  return await publicClient.waitForTransactionReceipt({ hash });
}

// Claim rewards from multiple validators
async function claimAllRewards(validatorAddresses: string[]) {
  const hash = await walletClient.writeContract({
    address: DISTRIBUTION_PRECOMPILE_ADDRESS,
    abi: DISTRIBUTION_PRECOMPILE_ABI,
    functionName: 'withdrawMultipleDelegationRewards',
    args: [validatorAddresses]
  });
  
  return await publicClient.waitForTransactionReceipt({ hash });
}

// Set withdrawal address
async function setRewardWithdrawalAddress(withdrawAddress: string) {
  const hash = await walletClient.writeContract({
    address: DISTRIBUTION_PRECOMPILE_ADDRESS,
    abi: DISTRIBUTION_PRECOMPILE_ABI,
    functionName: 'setWithdrawAddress',
    args: [withdrawAddress]
  });
  
  return await publicClient.waitForTransactionReceipt({ hash });
}

// Comprehensive reward management
async function manageRewards(delegatorAddress: string) {
  // 1. Check pending rewards
  const pendingRewards = await getPendingRewards(delegatorAddress);
  
  console.log('Pending rewards:');
  pendingRewards.totalRewards.forEach(reward => {
    console.log(`  ${reward.denom}: ${reward.amount}`);
  });
  
  // 2. Get validators with rewards
  const validatorsWithRewards = pendingRewards.validatorRewards
    .filter(reward => reward.coins.some(coin => parseFloat(coin.amount) > 0))
    .map(reward => reward.validator);
  
  if (validatorsWithRewards.length === 0) {
    console.log('No rewards to claim');
    return;
  }
  
  // 3. Claim all rewards efficiently
  if (validatorsWithRewards.length === 1) {
    console.log('Claiming rewards from single validator');
    return await claimRewardsFromValidator(validatorsWithRewards[0]);
  } else {
    console.log(`Claiming rewards from ${validatorsWithRewards.length} validators`);
    return await claimAllRewards(validatorsWithRewards);
  }
}

// Auto-compound rewards (claim and re-delegate)
async function autoCompoundRewards(
  delegatorAddress: string,
  preferredValidator: string
) {
  // This would require integration with staking precompile
  const rewards = await getPendingRewards(delegatorAddress);
  const totalSeiRewards = rewards.totalRewards.find(r => r.denom === 'usei');
  
  if (totalSeiRewards && parseFloat(totalSeiRewards.amount) > 0) {
    console.log(`Auto-compounding ${totalSeiRewards.amount} SEI`);
    
    // 1. Claim rewards
    const validatorsWithRewards = rewards.validatorRewards
      .filter(r => r.coins.some(c => c.denom === 'usei' && parseFloat(c.amount) > 0))
      .map(r => r.validator);
    
    await claimAllRewards(validatorsWithRewards);
    
    // 2. Re-delegate (would need staking precompile integration)
    console.log(`Would re-delegate to ${preferredValidator}`);
  }
}

Common Use Cases

Automated Reward Claiming

  • DeFi Protocols: Automatically claim and reinvest staking rewards
  • Yield Optimization: Compound rewards to maximize returns
  • Portfolio Management: Regular reward harvesting across multiple validators

Liquid Staking Protocols

  • Reward Distribution: Distribute claimed rewards to token holders
  • Fee Collection: Set withdrawal addresses to protocol treasury
  • Automated Compounding: Reinvest rewards to increase staking positions

Staking-as-a-Service

  • Client Management: Claim rewards on behalf of clients
  • Fee Deduction: Set withdrawal addresses for fee collection
  • Performance Tracking: Monitor reward generation across validators

Reward Optimization Strategies

Timing Optimization

// Check if rewards are worth claiming (considering gas costs)
async function shouldClaimRewards(
  delegatorAddress: string,
  minRewardThreshold: string = "1" // 1 SEI minimum
) {
  const rewards = await getPendingRewards(delegatorAddress);
  const totalSeiRewards = rewards.totalRewards.find(r => r.denom === 'usei');
  
  if (!totalSeiRewards) return false;
  
  const rewardAmount = parseFloat(totalSeiRewards.amount);
  const threshold = parseFloat(minRewardThreshold);
  
  return rewardAmount >= threshold;
}

Validator Selection for Claiming

// Prioritize validators with highest rewards
async function getOptimalClaimingOrder(delegatorAddress: string) {
  const rewards = await getPendingRewards(delegatorAddress);
  
  return rewards.validatorRewards
    .filter(reward => reward.coins.some(coin => parseFloat(coin.amount) > 0))
    .sort((a, b) => {
      const aSeiReward = a.coins.find(c => c.denom === 'usei');
      const bSeiReward = b.coins.find(c => c.denom === 'usei');
      
      const aAmount = aSeiReward ? parseFloat(aSeiReward.amount) : 0;
      const bAmount = bSeiReward ? parseFloat(bSeiReward.amount) : 0;
      
      return bAmount - aAmount; // Descending order
    })
    .map(reward => reward.validator);
}

Batch Claiming Strategy

// Determine optimal batching strategy
async function getOptimalClaimingStrategy(
  delegatorAddress: string,
  maxValidatorsPerTx: number = 10
) {
  const validatorsWithRewards = await getOptimalClaimingOrder(delegatorAddress);
  
  if (validatorsWithRewards.length <= maxValidatorsPerTx) {
    return {
      strategy: 'single_batch',
      batches: [validatorsWithRewards]
    };
  } else {
    const batches = [];
    for (let i = 0; i < validatorsWithRewards.length; i += maxValidatorsPerTx) {
      batches.push(validatorsWithRewards.slice(i, i + maxValidatorsPerTx));
    }
    
    return {
      strategy: 'multiple_batches',
      batches
    };
  }
}

Reward Tracking and Analytics

// Reward tracking utility
class RewardTracker {
  private claimedRewards: Array<{
    timestamp: number;
    validators: string[];
    amount: string;
    txHash: string;
  }> = [];
  
  recordClaim(validators: string[], amount: string, txHash: string) {
    this.claimedRewards.push({
      timestamp: Date.now(),
      validators,
      amount,
      txHash
    });
  }
  
  getTotalClaimed(): string {
    return this.claimedRewards
      .reduce((total, claim) => total + parseFloat(claim.amount), 0)
      .toString();
  }
  
  getClaimFrequency(): number {
    if (this.claimedRewards.length < 2) return 0;
    
    const timeSpan = this.claimedRewards[this.claimedRewards.length - 1].timestamp - 
                    this.claimedRewards[0].timestamp;
    
    return timeSpan / (this.claimedRewards.length - 1); // Average time between claims
  }
  
  getMostRewardingValidators(): string[] {
    const validatorRewards = new Map<string, number>();
    
    this.claimedRewards.forEach(claim => {
      const rewardPerValidator = parseFloat(claim.amount) / claim.validators.length;
      claim.validators.forEach(validator => {
        validatorRewards.set(
          validator, 
          (validatorRewards.get(validator) || 0) + rewardPerValidator
        );
      });
    });
    
    return Array.from(validatorRewards.entries())
      .sort((a, b) => b[1] - a[1])
      .map(entry => entry[0]);
  }
}

Error Handling

Common errors when using the Distribution precompile:
  • No rewards available: No pending rewards to claim
  • Invalid validator: Validator address doesn’t exist
  • Withdrawal address error: Invalid withdrawal address format
  • Insufficient gas: Complex multi-validator claims need more gas
async function safeClaimRewards(validatorAddresses: string[]) {
  try {
    // Validate inputs
    if (validatorAddresses.length === 0) {
      throw new Error('No validators specified');
    }
    
    // Check for pending rewards first
    const delegatorAddress = await signer.getAddress();
    const rewards = await getPendingRewards(delegatorAddress);
    
    const hasRewards = rewards.validatorRewards.some(reward =>
      validatorAddresses.includes(reward.validator) &&
      reward.coins.some(coin => parseFloat(coin.amount) > 0)
    );
    
    if (!hasRewards) {
      throw new Error('No rewards available for specified validators');
    }
    
    // Claim rewards
    if (validatorAddresses.length === 1) {
      return await claimRewardsFromValidator(validatorAddresses[0]);
    } else {
      return await claimAllRewards(validatorAddresses);
    }
  } catch (error) {
    if (error.message.includes('no rewards')) {
      throw new Error('No rewards available to claim');
    } else if (error.message.includes('validator not found')) {
      throw new Error('One or more validators not found');
    } else if (error.message.includes('out of gas')) {
      throw new Error('Insufficient gas - try claiming fewer validators at once');
    } else {
      throw error;
    }
  }
}
  • Staking: Delegate tokens to earn rewards
  • Bank: Check reward balances after claiming
  • Governance: Participate in governance with staked tokens
I