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:
- Epoch Identifier Matches: The
epochIdentifier
must match the configuredparams.EpochIdentifier
- Minting Has Started: The
epochNumber
must be >=params.MintingRewardsDistributionStartEpoch
- 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 occurredepoch_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 mintingmax_supply
: Configured maximum supplylast_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