Distribution Precompile
Address: 0x0000000000000000000000000000000000001007
The distribution precompile provides EVM access to Cosmos SDK’s distribution module. Smart contracts can manage staking rewards, validator commissions, and withdrawal addresses. This precompile is essential for DeFi applications that need to handle staking rewards programmatically.
Key Features
- Reward Management: Withdraw delegation rewards from validators
- Commission Handling: Validators can withdraw earned commissions
- Batch Operations: Withdraw from multiple validators efficiently
- Flexible Withdrawals: Set custom withdrawal addresses
- Comprehensive Queries: Access detailed reward information
Interface Overview
interface IDistr {
// Transaction Methods
function setWithdrawAddress(address withdrawAddr) external returns (bool);
function withdrawDelegationRewards(string memory validator) external returns (bool);
function withdrawMultipleDelegationRewards(string[] memory validators) external returns (bool);
function withdrawValidatorCommission(string memory validator) external returns (bool);
// Query Methods
function rewards(address delegatorAddress) external view returns (Rewards);
}Transaction Methods
setWithdrawAddress
Sets the withdrawal address for staking rewards. By default, rewards are sent to the delegator’s address, but this can be customized.
function setWithdrawAddress(address withdrawAddr) external returns (bool success);Parameters:
withdrawAddr: EVM address where future rewards should be sent
Gas Cost: ~30,000 gas
Example:
// Set rewards to go to a treasury contract
bool success = DISTR_CONTRACT.setWithdrawAddress(0x742d35Cc6634C0532925a3b8D4C9db96590c6C8C);
require(success, "Failed to set withdraw address");withdrawDelegationRewards
Withdraws accumulated rewards from a specific validator.
function withdrawDelegationRewards(string memory validator) external returns (bool success);Parameters:
validator: Sei validator address (e.g., “seivaloper1…”)
Gas Cost: ~50,000-80,000 gas (varies by reward amount)
Example:
string memory validator = "seivaloper1xyz...";
bool success = DISTR_CONTRACT.withdrawDelegationRewards(validator);
require(success, "Failed to withdraw rewards");withdrawMultipleDelegationRewards
Efficiently withdraws rewards from multiple validators in a single transaction.
function withdrawMultipleDelegationRewards(string[] memory validators) external returns (bool success);Parameters:
validators: Array of Sei validator addresses
Gas Cost: ~40,000 + (30,000 × number of validators)
Example:
string[] memory validators = new string[](3);
validators[0] = "seivaloper1abc...";
validators[1] = "seivaloper1def...";
validators[2] = "seivaloper1ghi...";
bool success = DISTR_CONTRACT.withdrawMultipleDelegationRewards(validators);
require(success, "Failed to withdraw multiple rewards");withdrawMultipleDelegationRewards is significantly more gas-efficient than multiple individual calls, especially when withdrawing from 3+ validators.withdrawValidatorCommission
Allows validators to withdraw their earned commission. Only callable by the validator operator.
function withdrawValidatorCommission(string memory validator) external returns (bool success);Parameters:
validator: Sei validator address (must match caller’s validator)
Gas Cost: ~60,000-90,000 gas
Example:
// Only works if caller is the validator operator
string memory myValidator = "seivaloper1myvalidator...";
bool success = DISTR_CONTRACT.withdrawValidatorCommission(myValidator);
require(success, "Failed to withdraw commission");Query Methods
rewards
Retrieves comprehensive reward information for a delegator across all validators.
function rewards(address delegatorAddress) external view returns (Rewards rewards);Data Structures:
struct Coin {
uint256 amount; // Token amount in 18 decimal precision (DecCoins)
uint256 decimals; // Always 18 - decimal precision for the amount
string denom; // Token denomination (e.g., "usei")
}
struct Reward {
Coin[] coins; // Reward coins from this validator
string validator_address; // Validator's Sei address
}
struct Rewards {
Reward[] rewards; // Per-validator breakdown
Coin[] total; // Total rewards across all validators
}Critical: Decimal Precision for Rewards
The rewards() query returns amounts with 18 decimal precision (DecCoins from Cosmos SDK).
This is different from the actual withdrawn reward amounts, which use 6 decimal precision (sdk.Coins).
To convert pending rewards to SEI for display:
const pendingRewardsSei = amount / 1e18; // rewards() query uses 18 decimalsWithdrawn rewards are in usei (6 decimals):
const withdrawnSei = withdrawnAmount / 1e6; // actual withdrawals use 6 decimalsExample:
Rewards memory userRewards = DISTR_CONTRACT.rewards(msg.sender);
// Check total rewards - uses 18 decimal precision
for (uint i = 0; i < userRewards.total.length; i++) {
Coin memory coin = userRewards.total[i];
// coin.decimals is always 18 for rewards
uint256 displayAmount = coin.amount / (10 ** coin.decimals);
// Process reward amount for coin.denom
}
// Check per-validator rewards
for (uint i = 0; i < userRewards.rewards.length; i++) {
Reward memory reward = userRewards.rewards[i];
// Process rewards from reward.validator_address
}Understanding Decimal Precision
The distribution precompile has different decimal precision for queries vs actual withdrawals due to how rewards are tracked in the Cosmos SDK:
| Function | Type | Decimal Precision | Conversion to SEI |
|---|---|---|---|
| rewards() | Query (pending rewards) | 18 decimals (DecCoins) | amount / 1e18 |
| withdrawDelegationRewards() | Withdrawn amount (event) | 6 decimals (usei) | amount / 1e6 |
| withdrawMultipleDelegationRewards() | Withdrawn amount (event) | 6 decimals (usei) | amount / 1e6 |
| withdrawValidatorCommission() | Withdrawn amount (event) | 6 decimals (usei) | amount / 1e6 |
Why Different Precisions?
-
Pending Rewards (18 decimals): The Cosmos SDK tracks pending rewards using
DecCoins(decimal coins) with 18 decimal precision for higher accuracy during reward accumulation. -
Withdrawn Rewards (6 decimals): When rewards are actually withdrawn, they are converted to
sdk.Coins(usei) with 6 decimal precision to match Sei’s native token units.
Conversion Helper Functions
// Convert pending rewards (18 decimals) to SEI
function pendingRewardsToSei(amount: bigint): number {
return Number(amount) / 1e18;
}
// Convert withdrawn rewards (6 decimals) to SEI
function withdrawnRewardsToSei(amount: bigint): number {
return Number(amount) / 1e6;
}
// Example usage
const pendingRewards = await distrContract.rewards(delegatorAddress);
const pendingSei = pendingRewardsToSei(pendingRewards.total[0].amount);
console.log(`Pending rewards: ${pendingSei} SEI`);Practical Examples
DeFi Yield Aggregator
contract YieldAggregator {
IDistr constant DISTR = IDistr(0x0000000000000000000000000000000000001007);
mapping(address => string[]) public userValidators;
function harvestRewards() external {
string[] memory validators = userValidators[msg.sender];
require(validators.length > 0, "No validators to harvest from");
// Efficiently withdraw from all validators
bool success = DISTR.withdrawMultipleDelegationRewards(validators);
require(success, "Harvest failed");
// Additional logic to compound or distribute rewards
}
function checkPendingRewards(address user) external view returns (uint256 totalSei) {
Rewards memory rewards = DISTR.rewards(user);
for (uint i = 0; i < rewards.total.length; i++) {
if (keccak256(bytes(rewards.total[i].denom)) == keccak256(bytes("usei"))) {
totalSei = rewards.total[i].amount;
break;
}
}
}
}Validator Commission Manager
contract ValidatorManager {
IDistr constant DISTR = IDistr(0x0000000000000000000000000000000000001007);
string public validatorAddress;
address public treasury;
constructor(string memory _validator, address _treasury) {
validatorAddress = _validator;
treasury = _treasury;
// Set commission withdrawals to go to treasury
DISTR.setWithdrawAddress(treasury);
}
function withdrawCommission() external {
bool success = DISTR.withdrawValidatorCommission(validatorAddress);
require(success, "Commission withdrawal failed");
}
}Error Handling
Common error scenarios and how to handle them:
contract SafeDistribution {
IDistr constant DISTR = IDistr(0x0000000000000000000000000000000000001007);
function safeWithdrawRewards(string memory validator) external returns (bool) {
// Check if there are rewards to withdraw first
Rewards memory rewards = DISTR.rewards(msg.sender);
bool hasRewards = false;
for (uint i = 0; i < rewards.rewards.length; i++) {
if (keccak256(bytes(rewards.rewards[i].validator_address)) ==
keccak256(bytes(validator))) {
hasRewards = rewards.rewards[i].coins.length > 0;
break;
}
}
if (!hasRewards) {
return false; // No rewards to withdraw
}
try DISTR.withdrawDelegationRewards(validator) returns (bool success) {
return success;
} catch {
return false; // Handle withdrawal failure gracefully
}
}
}Gas Optimization Tips
- Batch Operations: Use
withdrawMultipleDelegationRewardsfor multiple validators - Check Before Withdraw: Query rewards first to avoid unnecessary transactions
- Set Withdraw Address Once: Avoid repeated
setWithdrawAddresscalls - Commission Timing: Withdraw validator commission when amounts are substantial
Integration Patterns
Auto-Compounding Strategy
// Automatically reinvest rewards back into staking
function autoCompound() external {
// 1. Withdraw rewards
DISTR.withdrawMultipleDelegationRewards(getMyValidators());
// 2. Use staking precompile to re-delegate
// (Implementation depends on staking precompile integration)
}Treasury Management
// Route all rewards to a DAO treasury
function setupTreasuryWithdrawals(address treasury) external onlyOwner {
DISTR.setWithdrawAddress(treasury);
}