Skip to main content

Epoch Hooks

The mint module implements epoch hooks from the epochs module to perform minting at epoch boundaries rather than in an endblocker. This design provides predictable minting schedules aligned with longer time periods (e.g., weekly) rather than block-by-block.

Epoch Hooks Interface

The mint module implements the EpochHooks interface:

type EpochHooks interface {
BeforeEpochStart(ctx context.Context, epochIdentifier string, epochNumber int64) error
AfterEpochEnd(ctx context.Context, epochIdentifier string, epochNumber int64) error
}

BeforeEpochStart

Called before an epoch starts. The mint module does not perform any action in this hook.

func (k Keeper) BeforeEpochStart(ctx context.Context, epochIdentifier string, epochNumber int64) error {
// no-op
return nil
}

AfterEpochEnd

Called after an epoch ends. This is where all minting logic occurs.

Execution Conditions

The hook only mints tokens if all of the following conditions are met:

  1. Epoch Identifier Matches: The epochIdentifier must match the configured params.EpochIdentifier
  2. Minting Has Started: The epochNumber must be >= params.MintingRewardsDistributionStartEpoch
  3. Below Max Supply: Current total supply must be < params.MaxSupply

If any condition fails, the hook returns early without minting.

Minting Flow

When conditions are met, the hook performs the following operations:

1. Check and Apply Reduction

lastReductionEpoch = getLastReductionEpochNum()
if epochNumber >= lastReductionEpoch + params.ReductionPeriodInEpochs:
// Store old provisions for history
storeEpochProvisionsHistory(epochNumber - 1, minter.EpochProvisions)

// Apply reduction
minter.EpochProvisions = minter.EpochProvisions * params.ReductionFactor

// Store reduction history
storeReductionEpoch(epochNumber, params.ReductionFactor)

// Update last reduction epoch
setLastReductionEpochNum(epochNumber)

2. Calculate Mint Amount

mintedCoin = minter.EpochProvision(params)

// Check if minting would exceed max supply
currentSupply = bank.GetSupply(params.MintDenom)
newTotalSupply = currentSupply + mintedCoin.Amount

if newTotalSupply > params.MaxSupply:
// Adjust to mint only up to max supply
remainingToMint = params.MaxSupply - currentSupply
if remainingToMint > 0:
mintedCoin.Amount = remainingToMint
else:
// Already at max supply, stop
return nil

3. Mint Coins

bank.MintCoins(mintModuleAccount, mintedCoin)

4. Distribute Minted Coins

stakingAmount = mintedCoin.Amount * params.DistributionProportions.Staking
communityAmount = mintedCoin.Amount - stakingAmount

// Send to fee collector for staking rewards
bank.SendCoinsFromModuleToModule(mintModule, feeCollector, stakingAmount)

// Fund community pool if proportion > 0
if communityAmount > 0:
communityPoolKeeper.FundCommunityPool(communityAmount)

5. Update State and Emit Events

// Store current epoch provisions for historical tracking
storeEpochProvisionsHistory(epochNumber, minter.EpochProvisions)

// Emit minting event
emitEvent(TypeMintEpochEnd, {
epoch_number: epochNumber,
epoch_provisions: minter.EpochProvisions,
amount: mintedCoin.Amount,
total_supply_after: newTotalSupply,
max_supply: params.MaxSupply,
last_reduction_epoch: lastReductionEpoch,
})

Example Execution Timeline

Genesis State (Epoch 0-26)

Params.MintingRewardsDistributionStartEpoch = 27
Current epoch: 0-26
Action: Hook returns early, no minting

First Minting (Epoch 27)

Current epoch: 27
EpochProvisions: 76923076923075
Action:
- Initialize last_reduction_epoch = 27
- Mint 76923076923075 udt
- Distribute to staking (100%)
- Emit event

Regular Minting (Epochs 28-51)

Current epoch: 28-51
EpochProvisions: 76923076923075 (unchanged)
Action:
- Mint 76923076923075 udt each epoch
- Distribute according to proportions
- Emit events

First Reduction (Epoch 79)

Current epoch: 79 (= 27 + 52)
Last reduction: 27
ReductionPeriod: 52
Action:
- Store old provisions: epoch_provisions_history[78] = 76923076923075
- Apply reduction: 76923076923075 * 0.8 = 61538461538460
- Store reduction: reduction_epochs[79] = 0.8
- Update last_reduction_epoch = 79
- Mint 61538461538460 udt
- Distribute and emit event

Approaching Max Supply

Current epoch: X
Current supply: 99,950,000,000,000,000
Max supply: 100,000,000,000,000,000
Epoch provisions: 100,000,000,000,000
Action:
- Calculate: would mint 100T, but only 50T remaining
- Adjust: mint only 50,000,000,000,000
- Distribute and emit event

At Max Supply

Current epoch: X+1
Current supply: 100,000,000,000,000,000 (max reached)
Action:
- Check: supply >= max_supply
- Return early, no minting
- Log: "Max supply reached, stopping minting"

Events

TypeMintEpochEnd

Emitted after each successful minting operation.

Attributes:

  • epoch_number: The epoch number when minting occurred
  • epoch_provisions: Current epoch provisions (may have been reduced)
  • amount: Actual amount minted (may be less than provisions if near max supply)
  • total_supply_after: Total supply after minting
  • max_supply: Configured maximum supply
  • last_reduction_epoch: Epoch of last reduction

Error Handling

The hook is designed to be fault-tolerant:

  • If any operation fails (e.g., minting coins, sending coins), the error is returned and the entire state change is reverted
  • This ensures atomicity - either all minting operations succeed or none do
  • Errors are logged for debugging

Performance Considerations

  • Epoch-based execution: Minting once per epoch (e.g., weekly) is far more efficient than per-block minting
  • Lazy history storage: History is only stored when reductions occur or minting happens
  • Early returns: Multiple guard clauses ensure minimal computation when minting conditions aren't met
  • Predictable gas costs: Fixed operations per epoch make gas costs predictable