Skip to main content

Interest Payment

Overview

The Interest Payment contract provides a comprehensive solution for both interest-based payment schedules for loans backed by RestrictedLockupTokens and dividend distribution functionality for equity-like payments.

Key Features

Interest Payment Features

  • Configurable interest accrual start and end timestamps
  • Ability to create multiple payment periods with different interest rates
  • Admin can fund interest payments and force claim interest on behalf of token holders
  • Token holders can claim interest based on their ownership during specific periods
  • Support for principal amount funding and claiming at maturity
  • Comprehensive admin controls for managing interest distributions

Dividend Distribution Features

  • Snapshot-based distributions: Dividends are distributed based on token holdings at specific timestamps
  • Multiple token support: Distribute any ERC-20 token (USDC, DAI, etc.) as dividends
  • Proportional allocation: Dividends are allocated proportionally to token ownership at snapshot time
  • Batch operations: Efficient batch claiming and funding operations for multiple snapshots
  • Flexible funding: Multiple dividend rounds can be funded for the same snapshot timestamp
  • Audit trail: Complete tracking of funded, claimed, and unclaimed dividend amounts per snapshot
  • Integration with SnapshotPeriods: Leverages the same snapshot system used for interest calculations

Supported payment tokens

InterestPayment supports standard, fixed-balance ERC-20 tokens only. Fee-on-transfer (deflationary), rebasing / elastic-supply, and ERC777-callback tokens are not supported as the interest/principal payment token or as a dividend token.

This is enforced at the door: fundInterest, fundPrincipal and fundDividend each compare the contract's balance before and after the inbound transferFrom and revert with InterestPayment_InvalidFeeApplied if the received amount differs from the requested amount by any margin. The check is strict equality, so it rejects both a transfer fee and an unexpected balance increase (positive rebase, or a third-party push during an ERC777 hook).

The payout path is deliberately not guarded. Claims and reclaims settle the nominal amount recorded in the contract's internal accounting and do not reconcile against the live token balance. If a non-standard token is configured in spite of the constraint, behaviour on that path is undefined: an outbound transfer fee would silently underpay recipients while accounting records the full amount, and a negative rebase would leave the real balance below the outstanding obligation, so claims revert even though the views still report a claimable amount. There is no general sweep function to recover from either state — reclaimTotalDividend requires that no dividend has been claimed yet.

Two operational consequences:

  • paymentToken is immutable. It is fixed in the constructor and can never be changed, so the choice is a permanent, deploy-time decision. Verify the token before deployment.
  • Monitor solvency off-chain. Because accounting is counter-based, compare IERC20(token).balanceOf(<InterestPayment>) against the outstanding obligation (unused interest, unclaimed principal, and unused dividend funds) to detect drift before claimants hit a revert.

USDC, DAI and similar fixed-balance stablecoins satisfy the constraint.

Usage

How It Works

Interest payments are distributed to recipients based on their proportional ownership of the RestrictedLockupToken at specific time periods and the configured interest rates for each period. The contract keeps track of token ownership through the SnapshotPeriods contract, which records token balances at different timestamps. Interest rates can be set differently for each payment period, allowing for flexible interest payment schedules. Role checks (e.g., Transfer Admin, Contract Admin) are routed to the external AccessControl contract.

  1. Anyone funds the interest payment contract with a payment token (e.g., USDC) by calling fundInterest().
  2. Transfer Admin creates payment periods using createPaymentPeriod(), defining the start and end timestamps, interest rate, and interest rate period duration for the period.
  3. Token holders can claim their interest by calling claimInterest(amount) or claimInterestForPeriod(periodIdx, amount) with an optional amount cap.
  4. The contract calculates the amount of interest due based on the token holder's time-weighted ownership and the configured interest rate for each period.
  5. Interest is transferred to the token holder from the contract.

Principal Amount Management

The contract also supports principal amount management with partial operations:

  1. Transfer Admin can fund principal amounts using fundPrincipal(), in one call or in tranches. The pool may never exceed the full entitlement of the supply in circulation:

    totalAvailablePrincipalAmount() + amount <= principalAmountPerToken × (totalSupply - balanceOf(interestPayment))

    An overshoot reverts with InterestPayment_PrincipalFundingExceedsEntitlement(provided, maximum), since the excess is payment tokens only reclaimPrincipal() can recover.

    Redemption is open only while the pool covers that full entitlement. While principalRedemptionOpen() is false, every principal claim reverts with InterestPayment_PrincipalNotFullyFunded(funded, required) and availablePrincipalAmount() returns 0. This is what makes tranche funding safe: claimPrincipal pays out balance × principalAmountPerToken per holder regardless of how much is in the pool, so opening claims against a short pool would let the first claimants redeem in full until it ran dry and leave later holders with nothing.

    A pool that can never reach requiredPrincipalFunding() almost always means principalAmountPerToken was configured per whole token instead of per base unit — see Units of principalAmountPerToken. Such a pool funds without reverting but never opens redemption; reclaimPrincipal() gets the money back out.

  2. Token holders can claim their principal at maturity by calling claimPrincipal(amount):

    • amount = 0: Claims the token holder's full entitlement, balance × principalAmountPerToken. No capping applies — redemption is only open while the pool covers every entitlement
    • amount > 0: Claims exactly the specified amount (must be divisible by principalAmountPerToken)
  3. Transfer Admin can reclaim unused principal using reclaimPrincipal(amount):

    • amount = 0: Reclaims all unused principal
    • amount > 0: Reclaims exactly the specified amount
  4. The principal amount per token is set during contract deployment.

Operational notes:

  • While the pool is whole, claims preserve that. A principal claim soft-burns n base units and removes exactly n × principalAmountPerToken from the pool, so the pool continues to cover the remaining supply exactly. This is why a claim can never shut redemption part-way through a redemption run.
  • This does not hold for supply changes outside a principal claim. An admin burn(), burnHolding() or forceTransferBetween() on the security token changes the supply and touches nothing in the pool, so it moves the pool and the requirement independently — see the last two bullets.
  • principalRedemptionOpen() is live, not a latch. reclaimPrincipal() lowers the pool and newly minted supply raises the entitlement, and in either case redemption shuts again until the pool is funded back up. Re-read the view after any mint or reclaim instead of caching it; no event marks the pool going short. A partially funded pool distributes nothing — see Partial funding is not a haircut.
  • Once the pool is whole, a further top-up is only possible after new supply has been minted; the top-up covers the newly issued tokens, and PrincipalFullyFunded is emitted again when it completes.
  • If supply shrinks outside a principal claim, the pool becomes over-funded and fundPrincipal() reverts until new supply is minted or the excess is reclaimed with reclaimPrincipal(). Over-funded is a safe state: redemption is open there, because the pool more than covers what is outstanding.

Partial funding is not a haircut

A pool that falls short of requiredPrincipalFunding() distributes nothing. Every claim path reverts with InterestPayment_PrincipalNotFullyFunded(funded, required) and availablePrincipalAmount() returns 0, including forceClaimPrincipal() — a force claim is not a way around the gate.

This is deliberate. claimPrincipal pays each holder balance × principalAmountPerToken regardless of what is in the pool, so settling claims out of a short pool is first-come, first-served: the earliest claimants redeem in full and later holders get nothing. Refusing is the only outcome that treats holders equally.

It also means a deliberate partial repayment cannot be distributed through this contract. principalAmountPerToken is immutable, so there is no way to restate the payout rate downward to match a reduced pool. If an issuer can only repay part of the principal, the options are to fund and redeem at the full rate for a subset of supply, or to settle outside the contract; reclaimPrincipal() returns the funded tokens to the reclaimer either way. Supporting a declared haircut needs a rate that can be lowered before any claim, which is a separate change.

This diagram illustrates the principal funding and claiming process:

  1. Approve Funding: The Transfer Admin first approves the Interest Payment contract to spend payment tokens (like USDC) from their account.

  2. Fund Principal: The Transfer Admin calls fundPrincipal() with the amount to fund, in one call or in tranches, up to requiredPrincipalFunding() (principalAmountPerToken × circulatingBaseUnits). Redemption opens once the pool reaches that figure, and shuts again if a later mint or reclaim takes it back below.

  3. Wait for Maturity: Principal can only be claimed after reaching the maturity date (defined by interestAccrualEndTimestamp).

  4. Token Holder Approval: To claim principal, the token holder must first approve the Interest Payment contract to transfer their security tokens.

  5. Claim Principal: The token holder calls claimPrincipal(amount), where:

    • amount = 0: Claims all available principal for their tokens, capped at the principal that has actually been funded
    • amount > 0: Claims the specific amount (must be divisible by principalAmountPerToken and within the funded principal)
  6. Token Burn: The corresponding security tokens are burned from the token holder, effectively redeeming them.

  7. Principal Payment: The requested principal amount in payment tokens is transferred to the token holder.

This process allows token holders to redeem their security tokens for the principal amount at maturity, similar to how a bond works in traditional finance.

Dividend Distribution Functionality

The InterestPayment contract also provides comprehensive dividend distribution capabilities through the IDividends interface. This allows the same contract to handle both debt-like interest payments and equity-like dividend distributions.

How Dividend Distribution Works

Dividends are distributed based on historical token ownership at specific timestamps (snapshots). This approach ensures fair distribution regardless of when dividends are actually funded or claimed.

Key Dividend Operations

Funding Dividends
// Fund dividends for a specific snapshot timestamp
interestPayment.fundDividend(
dividendTokenAddress, // e.g., USDC contract address
dividendAmount, // Total amount to distribute (must be divisible by total supply)
snapshotTimestamp // Historical timestamp for ownership calculation
);

Requirements:

  • Dividend amount must be evenly divisible by the total token supply at the snapshot
  • Snapshot timestamp must be valid (within the contract's operational range)
  • Caller must have Transfer Admin role
  • Dividend token must be a valid ERC-20 contract
Claiming Dividends
// Individual claim
interestPayment.claimDividend(dividendTokenAddress, snapshotTimestamp, amount);

// Batch claim across multiple snapshots
uint256[] memory timestamps = [timestamp1, timestamp2, timestamp3];
uint256[] memory amounts = [0, amount2, amount3]
interestPayment.batchClaimDividend(dividendTokenAddress, timestamps, amounts);
Querying Dividend Information
// Check unclaimed dividend amount for an address
uint256 unclaimed = interestPayment.unclaimedBalanceAt(
dividendTokenAddress,
holderAddress,
snapshotTimestamp
);

// Check total dividend amount available to claim (claimed + unclaimed)
uint256 totalAwarded = interestPayment.totalAwardedBalanceAt(
dividendTokenAddress,
holderAddress,
snapshotTimestamp
);

// Check how much dividend funding is available for a snapshot
uint256 totalFunds = interestPayment.tokensAt(dividendTokenAddress, snapshotTimestamp);

Dividend Allocation Logic

Dividends are allocated proportionally based on token ownership at the snapshot timestamp:

Holder's Dividend = (Holder's Token Balance at Snapshot / Total Supply at Snapshot) × Total Dividend Amount

Example:

  • Total supply at snapshot: 1,000,000 tokens
  • Holder owns: 50,000 tokens (5%)
  • Total dividend funded: 100,000 USDC
  • Holder's share: 5,000 USDC

Multiple Dividend Rounds

The system supports multiple dividend distributions for the same snapshot:

// Initial dividend funding
interestPayment.fundDividend(usdcAddress, 100000e6, snapshotTimestamp);

// Additional dividend funding for same snapshot
interestPayment.fundDividend(usdcAddress, 50000e6, snapshotTimestamp);

// Total available dividends: 150,000 USDC

Integration with Interest Payments

The unified contract design allows for the following scenarios:

  1. Quarterly Dividends: Regular dividend distributions based on quarterly snapshots
  2. Interest + Dividends: Simultaneous debt service payments and equity distributions
  3. Special Distributions: One-time dividend payments for specific events
  4. Multi-Token Distributions: Different dividend tokens (USDC, DAI, etc.) for different purposes

Example: Complete Dividend Cycle

// 1. End of quarter - snapshot timestamp is recorded automatically
uint256 quarterEndTimestamp = block.timestamp;

// 2. Company profits are calculated off-chain
uint256 quarterlyProfits = 500000e6; // 500,000 USDC

// 3. Admin funds dividend distribution
usdcToken.approve(interestPaymentAddress, quarterlyProfits);
interestPayment.fundDividend(usdcAddress, quarterlyProfits, quarterEndTimestamp);

// 4. Token holders claim their dividends
interestPayment.claimDividend(usdcAddress, quarterEndTimestamp, amount);

This dividend functionality transforms the InterestPayment contract into a comprehensive tool for both debt and equity token management.

Administrative Functions

The Interest Payment contract provides several advanced administrative functions to adjust the terms of the interest and principal payments after deployment:

Extending Maturity (shiftInterestAccrualEnd)

The shiftInterestAccrualEnd(timestamp) function allows the Contract Admin or Transfer Admin to extend the maturity date by updating the interestAccrualEndTimestamp. This is particularly useful when:

  • The original maturity date needs to be extended due to business requirements
  • The loan or bond term is being renegotiated
  • Regulatory requirements necessitate a modification to the maturity date

The new timestamp must be greater than the current interestAccrualEndTimestamp, ensuring that maturity can only be extended, not reduced.

Managing Interest for Payment Periods

Creating Payment Periods

When creating payment periods with createPaymentPeriod(startTimestamp, endTimestamp, interestRate, interestRatePeriodDuration), the following requirements must be met:

  • Periods must be created sequentially, with no gaps between them
  • The end timestamp of one period must be the start timestamp of the next period
  • The first period must start at interestAccrualStartTimestamp
  • Each period's end timestamp must be a multiple of interestRatePeriodSeconds from the start timestamp, except for the final period which may end at interestAccrualEndTimestamp
  • Periods cannot be created for timestamps before interestAccrualStartTimestamp or after interestAccrualEndTimestamp
  • Interest rates cannot exceed maxInterestRate if set (0 means no limit)
  • interestRatePeriodDuration must be exactly 360, 365, or 366 days (in seconds) to prevent manipulation of interest rate calculations

This sequential creation ensures continuous tracking of token ownership across the entire interest accrual timeframe. Interest is calculated dynamically based on the rate and time-weighted token ownership rather than fixed amounts.

Updating Interest Rate for a Period

The updateInterestRateForPeriod(periodIdx, interestRate, interestRatePeriodDuration) function allows the Transfer Admin to modify the interest rate for a specific payment period before it starts. This enables:

  • Correcting interest rate configurations
  • Adjusting rates due to changing market conditions
  • Setting interest rate to zero for periods where no interest should accrue

Important: Interest rates can only be updated before the period starts (i.e., before block.timestamp > period.startTimestamp). The new rate cannot exceed maxInterestRate if set. The interestRatePeriodDuration must be exactly 360, 365, or 366 days (in seconds) to prevent manipulation of interest rate calculations.

Setting a period's interest rate to zero effectively means that no token holder will accrue interest during that period, regardless of their token ownership. This can be useful for:

  • Implementing grace periods
  • Reflecting periods of non-performance
  • Handling special situations where interest payments should be suspended

Managing Maximum Interest Rate

The setMaxInterestRate(maxInterestRate) function allows the Contract Admin to set a maximum interest rate cap. When set to 0, there is no limit on interest rates (subject to the absolute maximum). This provides:

  • Protection against accidentally setting excessively high interest rates
  • Governance control over maximum borrowing costs
  • Flexibility to adjust rate caps as market conditions change

Absolute Maximum Interest Rate

In addition to the configurable maxInterestRate, the contract enforces an absolute maximum interest rate of 1,000,000% (10^11 in basis points) via the MAX_ABSOLUTE_INTEREST_RATE constant. This hard limit:

If an interest rate exceeding MAX_ABSOLUTE_INTEREST_RATE is provided to createPaymentPeriod() or updateInterestRateForPeriod(), the transaction will revert with InterestPayment_InterestRateExceedsAbsoluteMax(provided, maximum).

These administrative functions provide considerable flexibility in managing the lifecycle of interest-bearing security tokens, allowing adjustments to both principal and interest terms while maintaining the integrity of the payment system.

Reclaiming Interest and Principal

In cases where interest or principal funds need to be reclaimed, administrators can use several functions to reclaim unused funds. This is often necessary in scenarios such as contract migration, correction of funding errors, or legal compliance requirements.

Reclaiming Interest

The Interest Payment contract provides multiple ways to reclaim interest:

  1. Setup: Contract Admin sets the reclaimer address that will receive all reclaimed funds
  2. Pause: Transfer Admin pauses payment for the specific period being reclaimed
  3. Reclaim Options:
    • reclaimInterest(wallet, paymentPeriodIdx): Reclaims unclaimed interest from a specific wallet for a period
    • reclaimInterestForAllRecipients(paymentPeriodIdx): Reclaims all unclaimed interest for an entire period
    • reclaimTotalInterest(amount): Reclaims a specific amount of unused interest
  4. Unpause: Transfer Admin unpauses payment for the period after reclaiming is complete

Reclaiming Principal

The principal amount can also be reclaimed if needed:

  1. Setup: Contract Admin sets the reclaimer address that will receive reclaimed funds
  2. Pause: Transfer Admin pauses the entire contract
  3. Reclaim: Transfer Admin reclaims all unused principal funds
  4. Unpause: Transfer Admin unpauses the contract after reclaiming is complete

Important Considerations

  • A valid reclaimer address must be set before any reclaiming can occur
  • The reclaimer must be an address outside the contract. setReclaimerAddress rejects the InterestPayment contract itself: reclaims debit the internal accounting and then transfer to the reclaimer, so a self-reclaimer would record the funds as paid out while they stayed in the contract, which has no sweep or rescue function
  • Pausing before reclaiming prevents users from claiming during the reclaim process
  • Unpausing after reclaiming allows normal operations to resume
  • Only Transfer Admin can reclaim funds, while Contract Admin can set the reclaimer address
  • Reclaimed funds are transferred directly to the reclaimer address

Early Repayment

The Interest Payment contract supports early repayment functionality, allowing Contract Admin or Transfer Admin to trigger an early maturity event. This feature is useful in scenarios such as:

  • Refinancing debt at a lower interest rate
  • Restructuring financial obligations
  • Implementing callable bonds that can be paid off before maturity
  • Responding to changes in market conditions

The earlyRepayment(timestamp) function performs two key actions:

  1. Sets the interest accrual end timestamp to the specified timestamp, effectively stopping any further interest accrual
  2. Pauses payment after the specified timestamp to provide administrative control over final payments

When early repayment is triggered:

  • Interest stops accruing at the specified timestamp, freezing the total interest amount
  • Token holders can still claim their accrued interest up to the repayment timestamp
  • Principal becomes available for claiming immediately (assuming it has been funded)
  • The EarlyRepayment event is emitted, recording the admin address and timestamp

This feature provides important flexibility in managing debt instruments represented by the token, allowing administrators to respond to changing financial conditions while preserving token holders' rights to receive their accrued interest and principal.

Force Claiming Interest

The Interest Payment contract provides administrative functions to force claim interest on behalf of wallet addresses. This is particularly useful in scenarios such as:

  • Processing interest distributions for wallets that may not have the means to claim independently
  • Managing automated distributions for institutional or managed accounts
  • Handling special settlement cases requiring administrative intervention
  • Distributing interest from paused payment periods for specific wallets

Two administrative force claim functions are available:

Force Claim for Specific Period (forceClaimForPeriod)

The forceClaimForPeriod(wallet, periodIdx, amount) function allows the Transfer Admin to force claim interest for a specific wallet from a specific payment period:

  • If the amount parameter is set to 0, all available interest for that wallet and period will be claimed
  • If the amount parameter is greater than 0, only that amount will be claimed (up to the available claimable amount)
  • This function works even for paused payment periods, providing administrative override capability
  • The ForceClaimed event is emitted with details of the transfer admin, recipient wallet, amount, and period index

Force Claim Across All Periods (forceClaim)

The forceClaim(wallet, amount) function allows the Transfer Admin to force claim interest for a specific wallet across all eligible payment periods:

  • Similar to forceClaimForPeriod, if amount is 0, all available interest is claimed
  • If amount is greater than 0, the function will claim up to that amount, potentially across multiple periods
  • The function processes periods sequentially, and will stop once the specified amount is reached
  • This function also works for paused payment periods
  • The ForceClaimed event is emitted for each period from which interest is claimed

Admin Functions

The Interest Payment contract has several admin functions for managing interest payments:

  • createPaymentPeriod(startTimestamp, endTimestamp, interestRate, interestRatePeriodDuration): Creates a new payment period with specified interest rate.
  • updateInterestRateForPeriod(periodIdx, interestRate, interestRatePeriodDuration): Updates the interest rate for a specific period before it starts.
  • setMaxInterestRate(maxInterestRate): Sets the maximum allowed interest rate (Contract Admin only).
  • fundInterest(amount): Funds the contract with interest payment tokens.
  • claimInterest(amount): Allows token holders to claim interest across all payment periods, with optional amount cap (0 for all available).
  • claimInterestForPeriod(periodIdx, amount): Enables token holders to claim interest for a specific payment period, with optional amount cap.
  • batchClaimInterestForPeriods(periodIdxs[], amount): Allows token holders to claim interest for multiple payment periods in a single transaction, with optional amount cap.
  • reclaimInterest(wallet, amount): Reclaims unclaimed interest from a wallet across all periods, with optional amount cap.
  • reclaimInterestForPeriod(wallet, paymentPeriodIdx, amount): Reclaims unclaimed interest from a wallet for a specific period, with optional amount cap.
  • batchReclaimInterest(wallet, periodIdxs[], amount): Reclaims unclaimed interest from a wallet for multiple periods, with optional amount cap.
  • reclaimInterestForAllRecipients(paymentPeriodIdx): Reclaims all unclaimed interest for a period.
  • reclaimTotalInterest(amount): Reclaims a specific amount of unused interest.
  • pausePaymentPeriod(periodIdx) and unpausePaymentPeriod(periodIdx): Pauses or unpauses claiming for a specific period.
  • pausePaymentAfter(timestamp): Pauses claiming after the given timestamp.
  • unpausePaymentAfter(): Unpause claiming.
  • pause(isPaused): Pauses or unpauses the entire contract.
  • shiftInterestAccrualEnd(timestamp): Updates the end timestamp for interest accrual for all payment periods. This allows the Transfer Admin to extend the maturity day.
  • setReclaimerAddress(reclaimerAddress): Sets the address that is authorized to receive reclaimed interest. This address must be set before any reclaim operations can be performed.
  • fundPrincipal(amount): Transfers principal payment tokens to the contract for distribution to token holders at maturity.
  • claimPrincipal(amount): Allows token holders to claim their principal amount at maturity based on their token holdings. Amount parameter: 0 = claim all available (capped at the funded principal), >0 = claim specific amount.
  • reclaimPrincipal(amount): Enables the Transfer Admin to reclaim unclaimed principal amounts from the contract. Amount parameter: 0 = reclaim all available, >0 = reclaim specific amount.
  • earlyRepayment(timestamp): Allows the Contract Admin or Transfer Admin to trigger early repayment of the loan at a specific timestamp. If timestamp is 0, uses current block timestamp.
  • forceClaim(wallet, amount): Allows Transfer Admin to force claim interest for a specific wallet across all payment periods, with optional amount cap (0 for all available).
  • forceClaimForPeriod(wallet, periodIdx, amount): Enables Transfer Admin to force claim interest for a specific wallet and payment period, with optional amount cap (0 for all available).

Interest Accrual and Calculation

The contract provides several view functions to check accrued interest:

  • totalAccruedInterest(): Returns the total accrued interest at the current time.
  • totalAccruedInterestAt(timestamp): Returns the total accrued interest at a specific timestamp.
  • accruedInterest(account): Returns the accrued interest for a specific account.
  • accruedInterestAt(account, timestamp): Returns the accrued interest for a specific account at a specific timestamp.
  • accruedInterestForPeriod(account, periodIdx): Returns the accrued interest for a specific account in a specific payment period.
  • paymentPeriodsCount(): Returns the total number of payment periods that have been created.
  • periodTotalInterest(periodIdx): Returns the total interest amount that was accrued during a specific payment period.
  • periodTotalClaimedInterest(periodIdx): Returns the total amount of interest that has been claimed by token holders for a specific payment period.
  • periodTotalReclaimedInterest(periodIdx): Returns the total amount of interest that has been reclaimed by the admin for a specific payment period.
  • periodAvailableInterest(periodIdx): Returns the amount of interest that is still available to be claimed for a specific payment period.
  • periodDuration(periodIdx): Returns the duration (in seconds) of a specific payment period.
  • periodStartTimestamp(periodIdx): Returns the start timestamp of a specific payment period.
  • periodEndTimestamp(periodIdx): Returns the end timestamp of a specific payment period.
  • unclaimedAmountAt(receiver, timestamp): Returns the amount of unclaimed interest for a specific receiver at a given timestamp.
  • unclaimedAmountForPeriod(receiver, periodIdx): Returns the amount of unclaimed interest for a specific receiver in a specific payment period.
  • claimedAmountForPeriod(receiver, periodIdx): Returns the amount of interest that has been claimed by a specific receiver in a specific payment period.
  • reclaimedAmountForPeriod(receiver, periodIdx): Returns the amount of interest that has been reclaimed from a specific receiver in a specific payment period.
  • usedAmountForPeriod(receiver, periodIdx): Returns the total amount of interest that has been either claimed or reclaimed for a specific receiver in a specific payment period.
  • accountTotalClaimedAmount(receiver): Returns the total amount of interest that has been claimed by a specific receiver across all payment periods.
  • accountTotalReclaimedAmount(receiver): Returns the total amount of interest that has been reclaimed from a specific receiver across all payment periods.
  • totalInterestAmountFunded(): Returns the total amount of interest that has been funded to the contract.
  • totalInterestAmountClaimed(): Returns the total amount of interest that has been claimed by all token holders.
  • totalInterestAmountReclaimed(): Returns the total amount of interest that has been reclaimed by admins.
  • totalInterestAmountUnused(): Returns the total amount of interest that remains unused in the contract.
  • nearestInterestPaymentTimestampAt(timestamp): Returns the nearest interest payment timestamp at a given timestamp (rounded to period boundaries).
  • findPaymentPeriodIndex(startTimestamp, endTimestamp): Finds the index of a payment period that covers the specified time range.
  • paymentPeriodPaused(periodIdx): Returns whether a specific payment period is paused.
  • fundedPrincipalAmount(): Returns the total amount of principal that has been funded to the contract.
  • reclaimPrincipalAmount(): Returns the total amount of principal that has been reclaimed by the admin.
  • claimedPrincipalAmount(): Returns the total amount of principal that has been claimed by token holders.
  • totalAvailablePrincipalAmount(): Returns the total amount of principal that is still available to be claimed.
  • availablePrincipalAmount(account): Returns the amount of principal that is available to be claimed by a specific account.
  • requiredPrincipalFunding(): Returns the payment-token amount needed to fully fund principal for the current circulating supply (circulatingBaseUnits × principalAmountPerToken). This is the ceiling fundPrincipal enforces on the running total, not a figure it must equal — the pool may be filled in tranches — and the level the pool must reach before redemption opens.

Interest is accrued based on configurable interest rates and calculated using the formula:

interest = (interestRatePerSecond × principalAmountPerToken × ownershipForPeriod) / (INTEREST_RATE_PRECISION_FACTOR × BIPS_PRECISION)

Where:

  • ownershipForPeriod = time-weighted ownership = token balance in base units × time period duration (in seconds)
  • interestRatePerSecond = (interestRate × INTEREST_RATE_PRECISION_FACTOR) / interestRatePeriodDuration
  • INTEREST_RATE_PRECISION_FACTOR = 10^24 (constant) for interest rate calculations
  • BIPS_PRECISION = 10,000,000 for basis points calculations

The interest rate is specified in basis points (bips) where 10,000,000 bips = 100%. For example, a 5% annual interest rate would be specified as 500,000 bips.

Precision and Overflow Protection

The contract uses OpenZeppelin's Math.mulDiv function for all interest calculations, which provides:

  1. 512-bit intermediate arithmetic: Prevents overflow in multiplication before division
  2. Maximum precision: Single division operation minimizes rounding errors
  3. Simplified architecture: Fixed precision factor (10^24) works for all deployment scenarios

Purpose of INTEREST_RATE_PRECISION_FACTOR:

The precision factor prevents precision loss when dividing by interestRatePeriodDuration. Without it, small rates would lose precision immediately.

Example:

  • Interest rate: 5% = 500,000 (in BIPS, where 10,000,000 = 100%)
  • Period duration: 365 days = 31,536,000 seconds

Without precision factor:

500,000 / 31,536,000 = 0 (loses everything!)

With precision factor (10^24):

(500,000 × 10^24) / 31,536,000 = 15,854,895,991,882,091,391 (preserves precision)

How Math.mulDiv Prevents Overflow:

The Math.mulDiv(a, b, c) function computes (a × b) / c using 512-bit intermediate precision. This allows safe computation even when a × b would overflow a 256-bit integer.

// Interest calculation uses single mulDiv for maximum precision
return Math.mulDiv(
interestRatePerSecond * principalAmountPerToken,
ownershipForPeriod,
INTEREST_RATE_PRECISION_FACTOR * BIPS_PRECISION
);

Units of principalAmountPerToken

principalAmountPerToken is denominated in payment-token base units per security-token BASE UNIT — not per whole token.

This is the single most important configuration value to get right. Every calculation in the contract multiplies it by a raw balanceOf result, which is expressed in base units:

  • interest: interestRatePerSecond × principalAmountPerToken × ownershipForPeriod / (10^24 × 10^7)
  • principal: balanceOf(holder) × principalAmountPerToken

Configuring it as a per-whole-token face value inflates every entitlement by 10 ** securityTokenDecimals.

Converting a face value to principalAmountPerToken:

principalAmountPerToken = faceValuePerWholeToken / 10 ** securityTokenDecimals

The division must be exact. That is why the constructor requires the security token's decimals() to be less than or equal to the payment token's decimals(), reverting with InterestPayment_IncompatibleTokenDecimals otherwise — see the constructor's Requirements in the API Reference. Note that the guard is necessary but not sufficient: it only guarantees an exact division for face values that are whole multiples of 10 ** securityTokenDecimals.

Security token decimalsPayment tokenFace value per whole tokenprincipalAmountPerToken
6USDC (6)$100.00100e6 / 1e6 = 100
2USDC (6)$1.001e6 / 1e2 = 10000

Granularity limit. Because principalAmountPerToken is a whole number of payment base units per security base unit, the smallest expressible face value per whole token is 10 ** securityTokenDecimals payment base units. With an 18-decimal security token and USDC that would be 1,000,000,000,000,whichmakessuchapairingunusableinpracticeandtheconstructorrejectsanysecuritytokendecimalsgreaterthanthepaymenttokensoutright,sincenointegerprincipalAmountPerTokencouldexpressanordinaryfacevalueinthatcase.Chooseasecuritytokendecimalsstrictlybelowthepaymenttokensdecimalsifyouneedsubdollarfacevalues.Theconstructoronlyrejectsasecuritytokenwithmoredecimalsthanthepaymenttoken,sotheequaldecimalscase(6/6withUSDC)ispermittedbuttherethesmallestexpressiblefacevalueisexactly106paymentbaseunits,i.e.1,000,000,000,000, which makes such a pairing unusable in practice — and the constructor rejects any security-token `decimals` greater than the payment token's outright, since no integer `principalAmountPerToken` could express an ordinary face value in that case. **Choose a security-token `decimals` strictly below the payment token's `decimals`** if you need sub-dollar face values. The constructor only rejects a security token with *more* decimals than the payment token, so the equal-decimals case (6/6 with USDC) is permitted — but there the smallest expressible face value is exactly `10 ** 6` payment base units, i.e. 1.00, and a $0.50 face value cannot be expressed at all. More generally the guard guarantees an integer principalAmountPerToken only for face values that are whole multiples of 10 ** securityTokenDecimals.

Verifying a deployment. Call principalAmountPerWholeToken() after deploying. It returns principalAmountPerToken() × 10 ** securityTokenDecimals, i.e. the face value the contract will actually pay per whole token. If that number is not the intended face value, the configuration is wrong — fix it before funding. A wrong-unit configuration can still be funded, but its requiredPrincipalFunding() is 10 ** securityTokenDecimals too large to ever reach, so principalRedemptionOpen() stays false and no holder can ever redeem; reclaimPrincipal() is then the only way to get the payment tokens back.

Best Practices:

  • Derive principalAmountPerToken with the formula above, then confirm it with principalAmountPerWholeToken()
  • Fund up to requiredPrincipalFunding() (circulatingBaseUnits × principalAmountPerToken) — never a whole-token-supply-based amount; the latter is 10 ** securityTokenDecimals short, so it funds but never opens redemption. Check principalRedemptionOpen() after funding, and re-check it after any mint or reclaim — it is a live predicate and can return to false
  • The principalAmountPerToken is validated at deployment to not exceed MAX_PRINCIPAL_AMOUNT_PER_TOKEN (10^47), ensuring safe multiplication with interestRatePerSecond without overflow. That bound covers the interest product only, not circulatingBaseUnits × principalAmountPerToken; requiredPrincipalFunding() and principalAmountPerWholeToken() saturate at type(uint256).max rather than reverting if that product would overflow, which leaves redemption permanently shut and reclaimPrincipal() as the exit

Interest Rate Period Duration Validation

The interestRatePeriodDuration parameter used in createPaymentPeriod() and updateInterestRateForPeriod() functions is strictly validated to prevent manipulation of interest rate calculations. This parameter must be exactly one of the following values:

  • 360 days (31,104,000 seconds) - Standard banking year
  • 365 days (31,536,000 seconds) - Calendar year
  • 366 days (31,622,400 seconds) - Leap year

This validation prevents administrators from bypassing interest rate limits by manipulating the duration parameter. For example, setting an extremely small duration (like 1 second) would artificially inflate the calculated interestRatePerSecond, effectively bypassing the maxInterestRate validation.

Security Impact:

  • Ensures consistent and predictable interest rate calculations
  • Prevents manipulation of the interest rate calculation formula
  • Maintains the integrity of governance-set interest rate limits
  • Protects against economic parameter violations

If an invalid duration is provided, the transaction will revert with InterestPayment_InvalidInterestRatePeriodDuration().

Amount Parameter Behavior

All claiming and reclaiming functions support an amount parameter that allows users to specify how much they want to claim or reclaim:

  • amount = 0: Claims/reclaims all available funds
  • amount > 0: Claims/reclaims up to the specified amount, capped at available funds
  • If the specified amount exceeds available funds, the transaction will revert with InterestPayment_NotEnoughFundsToClaim

This provides fine-grained control over fund movements and enables partial claiming/reclaiming scenarios.

Principal Amount Parameter Behavior

Principal claiming and reclaiming functions also support an amount parameter for precise control:

For claimPrincipal(amount):

  • amount = 0: Claims all available principal for the token holder's current balance
    • The entitlement (tokenBalance × principalAmountPerToken) is capped at the unused funded principal, then rounded down to a whole number of tokens
    • If the contract is only partially funded, the holder receives the funded portion and keeps the rest of their token balance to claim once more principal is funded
    • Reverts with InterestPayment_NoFundsToClaim when the unused principal is smaller than principalAmountPerToken
  • amount > 0: Claims exactly the specified amount, with requirements:
    • Amount must not exceed the holder's available principal (tokenBalance × principalAmountPerToken, with tokenBalance in base units)
    • Amount must not exceed the unused funded principal
    • Amount must be divisible by principalAmountPerToken to ensure exact token-to-principal conversion
    • Corresponding tokens are burned: tokensToBurn = amount / principalAmountPerToken

availablePrincipalAmount(account) returns exactly what claimPrincipal(0) would claim for that account whenever it is non-zero, so a non-zero result can be passed straight back in as amount. A result of 0 must not be forwarded: 0 is the claim-everything sentinel, so passing it asks for the opposite of what the view reported. Distinguish the cases with principalRedemptionOpen().

For reclaimPrincipal(amount):

  • amount = 0: Reclaims all unused principal from the contract
  • amount > 0: Reclaims exactly the specified amount, capped at available unused principal

This enables use cases such as:

  • Partial redemption of token positions
  • Staged principal withdrawals
  • Precise fund management for complex financial instruments

Wallets with many holdings. claimPrincipal(0) burns the full balance of the holder in one call. The token burns the holdings in FIFO order. The gas cost is therefore a function of the count of different holdings in the wallet. The cost is approximately 4,787 gas for each holding. Refer to "Gas Cost and Limits" in docs/restricted-lockup-token.md. One full claim can need more gas than the block gas limit, if the wallet has some thousands of different (tokenType, mintDay) holdings.

To correct this condition, redeem the balance in more than one claimPrincipal(amount) call. The gas cost of each call is a function of the amount, and not of the count of holdings in the wallet. The FIFO burn clears the bitmap bit of each holding that it uses. Each subsequent call therefore starts after the holdings of the earlier call. Each amount must still divide exactly by principalAmountPerToken. A transfer admin can do the same operation for the holder with forceClaimPrincipal(wallet, amount).

Error Handling

Administrative Errors

  • InterestPayment_InvalidRestrictedLockupTokenAddress: Thrown when attempting to deploy with an invalid token address
  • InterestPayment_InvalidDividendTokenAddress: Thrown when an invalid dividend token address is provided
  • InterestPayment_InvalidPaymentPeriodSeconds: Thrown when payment period is set to zero
  • InterestPayment_InvalidReclaimerAddress: Thrown at reclaim time when the reclaimer is unset (address(0)), and at set time when attempting to set the InterestPayment contract itself as reclaimer
  • InterestPayment_InvalidRecipientAddress: Thrown when a force-claim names the InterestPayment contract itself as the recipient wallet
  • InterestPayment_InvalidTimestamp: Thrown when providing an invalid timestamp for payment pausing
  • InterestPayment_InvalidPaymentToken: Thrown when payment token address is invalid
  • InterestPayment_InterestRateGreaterThanMaxInterestRate: Thrown when trying to set interest rate above max rate
  • InterestPayment_PeriodAlreadyStarted: Thrown when trying to update interest rate after period has started
  • InterestPayment_InterestRateNotChanged: Thrown when trying to update interest rate to the same value
  • InterestPayment_MaxInterestRateNotChanged: Thrown when trying to set max interest rate to the same value

Configuration Errors

  • InterestPayment_InvalidInterestAccrualStartTimestamp: Thrown when start timestamp is set to zero
  • InterestPayment_InvalidInterestAccrualEndTimestamp: Thrown when end timestamp is set to zero
  • InterestPayment_InvalidInterestAccrualPeriod: Thrown when start timestamp is greater than or equal to end timestamp
  • InterestPayment_CannotUnpauseAfterMaturity: Thrown when attempting to unpause payments after maturity
  • InterestPayment_PaymentNotPausedAfter: Thrown when trying to unpause when not paused

Period Management Errors

  • InterestPayment_InvalidPeriod: Thrown when referencing a non-existent period
  • InterestPayment_StartTimestampGreaterThanEndTimestamp: Thrown when creating a period with start after end
  • InterestPayment_StartTimestampBeforeAccrualStartTimestamp: Thrown when period starts before overall interest accrual start
  • InterestPayment_EndTimestampGreaterThanAccrualEndTimestamp: Thrown when period ends after overall interest accrual end
  • InterestPayment_NoPaymentPeriods: Thrown when attempting to claim interest when no payment periods have been created
  • InterestPayment_PeriodDurationNotMultipleOfInterestRatePeriod: Thrown when a payment period's duration does not align with the configured interest rate period. For example, if the interest rate period is set to 1 day (86400 seconds), then all payment periods must start and end at the same time of day. If interest accrual begins at 2025-01-01 12:00:00, then valid period boundaries would be 2025-01-01 12:00:00 to 2025-01-02 12:00:00, 2025-01-02 12:00:00 to 2025-01-03 12:00:00, etc. This ensures consistent interest accrual calculations across all periods.
  • InterestPayment_PeriodAlreadyExists: Thrown when attempting to create a duplicate period
  • InterestPayment_PeriodNotNext: Thrown when attempting to create a payment period that does not immediately follow the previous period. For example, if the last payment period ends at 2025-02-14 12:00:00, the next period must start at exactly that timestamp. Creating a period starting at 2025-02-24 12:00:00 would fail because it creates a gap in the payment schedule. This ensures continuous, sequential payment periods without any gaps in the interest accrual timeline.
  • InterestPayment_InvalidTotalAccruedInterest: Thrown when setting accrued interest below already claimed/reclaimed amount
  • InterestPayment_PaymentPeriodPaused: Thrown when attempting to claim from a paused period

Funding and Claiming Errors

  • InterestPayment_InvalidAmount: Thrown when attempting to fund with zero amount
  • InterestPayment_TokenSupplyIsZero: Thrown when token supply is zero during principal funding
  • InterestPayment_PrincipalFundingExceedsEntitlement: Thrown when fundPrincipal's running total would exceed requiredPrincipalFunding()
  • InterestPayment_PrincipalNotFullyFunded: Thrown by a principal claim while the pool does not cover requiredPrincipalFunding()
  • InterestPayment_IncompatibleTokenDecimals: Thrown by the constructor when the security token's decimals() exceeds the payment token's. Carries both values
  • InterestPayment_PrincipalAmountNotDivisibleByTokenSupply: Thrown when a partial claimPrincipal amount isn't evenly divisible by principalAmountPerToken. The name predates the check it now guards — it is divisibility by the rate, not by token supply; inspect principalAmountPerToken, not totalSupply
  • InterestPayment_IncompatibleTokenDecimals: Thrown at deployment when the security token's decimals exceed the payment token's decimals
  • InterestPayment_InvalidTokenDecimals: Thrown by fundDividend when the security token's decimals exceed the dividend token's decimals
  • InterestPayment_InvalidToken: Thrown when using an invalid token for payment
  • InterestPayment_InvalidFeeApplied: Thrown when token transfer results in unexpected balance
  • InterestPayment_NoFundsToClaim: Thrown when attempting to claim with no available funds
  • InterestPayment_NotEnoughFundsToClaim: Thrown when attempting to force claim more than available
  • InterestPayment_InvalidUnclaimedAmount: Thrown when unclaimed calculation produces invalid result
  • InterestPayment_NotEnoughFundedPrincipal: Thrown when attempting to claim more principal than available
  • InterestPayment_MaturityNotReached: Thrown when attempting to claim principal before maturity

API Reference

Constructor

constructor(ConstructorParams params)

Initializes the InterestPayment contract with configuration parameters.

Parameters:

  • params (ConstructorParams): Struct containing initialization parameters

ConstructorParams Structure:

struct ConstructorParams {
address accessControl; // External access control contract address
address restrictedLockupTokenAddress; // RestrictedLockupToken contract address
address trustedForwarder; // ERC-2771 trusted forwarder address
address paymentToken; // ERC-20 token used for payments
uint256 paymentPeriodSeconds; // Interest rate period duration in seconds
uint256 principalAmountPerToken; // Payment-token base units per BASE UNIT of the security token
uint256 interestAccrualStartTimestamp; // When interest accrual begins
uint256 interestAccrualEndTimestamp; // When interest accrual ends (maturity)
uint256 maxInterestRate; // Maximum allowed interest rate in basis points
}

Requirements:

  • All addresses must be non-zero
  • Payment period seconds must be greater than zero
  • Interest accrual start must be before end timestamp
  • Principal amount per token must be greater than zero and must not exceed MAX_PRINCIPAL_AMOUNT_PER_TOKEN
  • The payment token must expose decimals(), and its address must have contract code — otherwise the constructor reverts with InterestPayment_InvalidPaymentToken
  • The security token's decimals() must not exceed the payment token's decimals(), or the deployment reverts with InterestPayment_IncompatibleTokenDecimals(securityTokenDecimals, paymentTokenDecimals). This mirrors the guard already applied on the dividend path in fundDividend. It guarantees a face value per whole token can be expressed exactly as faceValuePerWholeToken / 10 ** securityTokenDecimals only for face values that are whole multiples of 10 ** securityTokenDecimals — at equal decimals the floor is one whole payment-token unit.

Constants

CONTRACT_ADMIN_ROLE() → uint8

Returns the contract admin role constant (1).

Returns:

  • uint8: The contract admin role value

RESERVE_ADMIN_ROLE() → uint8

Returns the reserve admin role constant (2).

Returns:

  • uint8: The reserve admin role value

WALLETS_ADMIN_ROLE() → uint8

Returns the wallets admin role constant (4).

Returns:

  • uint8: The wallets admin role value

TRANSFER_ADMIN_ROLE() → uint8

Returns the transfer admin role constant (8).

Returns:

  • uint8: The transfer admin role value

INTEREST_RATE_PRECISION_FACTOR() → uint256

Returns the precision factor used for interest rate calculations (10^24 = 1,000,000,000,000,000,000,000,000).

Returns:

  • uint256: The constant precision factor (10^24) for interest calculations

MAX_PRINCIPAL_AMOUNT_PER_TOKEN() → uint256

Returns the maximum allowed principal amount per token (10^47 = 100,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000).

Returns:

  • uint256: The maximum allowed value for principalAmountPerToken to prevent overflow in interest calculations

MAX_ABSOLUTE_INTEREST_RATE() → uint256

Returns the absolute maximum allowed interest rate (10^11 = 100,000,000,000), representing 1,000,000% APR.

Returns:

  • uint256: The absolute maximum interest rate that can be set, regardless of maxInterestRate configuration

Note: This constant prevents overflow in interest calculations by ensuring interestRatePerSecond * principalAmountPerToken cannot exceed uint256 bounds. Any attempt to set an interest rate above this value will revert with InterestPayment_InterestRateExceedsAbsoluteMax.


Administrative Functions (API)

pause(bool isPaused_)

Pauses or unpauses the contract.

Parameters:

  • isPaused_ (bool): True to pause, false to unpause

Requirements:

  • Caller must have CONTRACT_ADMIN_ROLE or TRANSFER_ADMIN_ROLE

Emits: Paused or Unpaused events


pausePaymentPeriod(uint256 periodIdx)

Pauses a specific payment period.

Parameters:

  • periodIdx (uint256): Index of the payment period to pause

Requirements:

  • Caller must have CONTRACT_ADMIN_ROLE or TRANSFER_ADMIN_ROLE
  • Period index must be valid

Emits: PeriodPaused(authority, periodIdx)


unpausePaymentPeriod(uint256 periodIdx)

Unpauses a specific payment period.

Parameters:

  • periodIdx (uint256): Index of the payment period to unpause

Requirements:

  • Caller must have CONTRACT_ADMIN_ROLE or TRANSFER_ADMIN_ROLE
  • Period index must be valid

Emits: PeriodUnpaused(authority, periodIdx)


pausePaymentAfter(uint256 timestamp)

Pauses all payments after a specific timestamp.

Parameters:

  • timestamp (uint256): Timestamp after which payments are paused

Requirements:

  • Caller must have CONTRACT_ADMIN_ROLE or TRANSFER_ADMIN_ROLE
  • Timestamp must be in the future

Emits: PaymentPausedAfter(timestamp)


unpausePaymentAfter()

Removes the payment pause timestamp.

Requirements:

  • Caller must have CONTRACT_ADMIN_ROLE or TRANSFER_ADMIN_ROLE
  • Payments must currently be paused after a timestamp
  • Current time must be before maturity

Emits: PaymentUnpausedAfter()


setReclaimerAddress(address newReclaimerAddress)

Sets the address that receives reclaimed funds.

Parameters:

  • newReclaimerAddress (address): New reclaimer address

Requirements:

  • Caller must have CONTRACT_ADMIN_ROLE
  • newReclaimerAddress must not be the InterestPayment contract itself, otherwise reverts with InterestPayment_InvalidReclaimerAddress
  • address(0) is accepted and disables reclaiming: with no reclaimer configured, every reclaim entrypoint reverts with InterestPayment_InvalidReclaimerAddress at call time

Emits: ReclaimerAddressChanged(authority, newReclaimerAddress)


shiftInterestAccrualEnd(uint256 newInterestAccrualEndTimestamp)

Extends the interest accrual end timestamp (maturity date).

Parameters:

  • newInterestAccrualEndTimestamp (uint256): New end timestamp

Requirements:

  • Caller must have CONTRACT_ADMIN_ROLE or TRANSFER_ADMIN_ROLE
  • New timestamp must be after current end timestamp

Emits: InterestAccrualEndShifted(authority, newInterestAccrualEndTimestamp)


setMaxInterestRate(uint256 maxInterestRate_)

Sets the maximum allowed interest rate.

Parameters:

  • maxInterestRate_ (uint256): New maximum interest rate in basis points

Requirements:

  • Caller must have CONTRACT_ADMIN_ROLE
  • Rate must be different from current rate

Emits: SetMaxInterestRate(authority, maxInterestRate_)


earlyRepayment(uint256 timestamp)

Triggers early repayment by pausing payments and updating maturity.

Parameters:

  • timestamp (uint256): New maturity timestamp

Requirements:

  • Caller must have CONTRACT_ADMIN_ROLE or TRANSFER_ADMIN_ROLE
  • Timestamp must be in the future

Emits: EarlyRepayment(authority, timestamp)


Payment Period Management

createPaymentPeriod(uint256 startTimestamp, uint256 endTimestamp, uint256 interestRate_, uint256 interestRatePeriodDuration)

Creates a new payment period with specified interest rate.

Parameters:

  • startTimestamp (uint256): Period start timestamp
  • endTimestamp (uint256): Period end timestamp
  • interestRate_ (uint256): Interest rate in basis points
  • interestRatePeriodDuration (uint256): Duration for interest rate calculation (must be 360, 365, or 366 days)

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Start must be before end timestamp
  • Period must be sequential (no gaps)
  • Interest rate must not exceed maximum
  • interestRatePeriodDuration must be exactly 360, 365, or 366 days (in seconds)

Emits: PaymentPeriodCreated(authority, periodIdx, startTimestamp, endTimestamp, interestRate_)


updateInterestRateForPeriod(uint256 periodIdx, uint256 interestRate_, uint256 interestRatePeriodDuration)

Updates the interest rate for an existing period before it starts.

Parameters:

  • periodIdx (uint256): Index of the period to update
  • interestRate_ (uint256): New interest rate in basis points
  • interestRatePeriodDuration (uint256): Duration for interest rate calculation (must be 360, 365, or 366 days)

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Period must not have started yet
  • Interest rate must be different from current rate
  • Rate must not exceed maximum
  • interestRatePeriodDuration must be exactly 360, 365, or 366 days (in seconds)

Emits: InterestRateUpdated(authority, periodIdx, interestRate_)


Interest Funding and Management

fundInterest(uint256 amount)

Funds the contract with payment tokens for interest distributions.

Parameters:

  • amount (uint256): Amount of payment tokens to fund

Requirements:

  • Contract must not be paused
  • Amount must be greater than zero
  • Caller must have approved tokens to contract

Emits: Funded(funder, amount)


claimInterest(uint256 amount)

Claims available interest for the caller across all periods.

Parameters:

  • amount (uint256): Maximum amount to claim (0 = claim all available)

Requirements:

  • Contract must not be paused
  • Must have available interest to claim
  • Periods must not be paused

Emits: Claimed(claimer, amount, periodIdx) for each period


claimInterestForPeriod(uint256 paymentPeriodIdx, uint256 amount)

Claims interest for a specific payment period.

Parameters:

  • paymentPeriodIdx (uint256): Index of the payment period
  • amount (uint256): Amount to claim (0 = claim all available for period)

Requirements:

  • Contract must not be paused
  • Period must exist and not be paused
  • Must have available interest for the period

Emits: Claimed(claimer, amount, paymentPeriodIdx)


batchClaimInterestForPeriods(uint256[] paymentPeriodIdxs, uint256 amount)

Claims interest across multiple specified periods.

Parameters:

  • paymentPeriodIdxs (uint256[]): Array of payment period indices
  • amount (uint256): Maximum total amount to claim across periods

Requirements:

  • Contract must not be paused
  • All periods must exist and not be paused
  • Must have available interest in the periods

Emits: Claimed(claimer, amount, periodIdx) for each period


forceClaim(address wallet, uint256 amount)

Force claims interest on behalf of a wallet (admin function).

Parameters:

  • wallet (address): Address to claim interest for
  • amount (uint256): Maximum amount to claim (0 = claim all available)

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • wallet must not be the InterestPayment contract itself, otherwise reverts with InterestPayment_InvalidRecipientAddress

Emits: ForceClaimed(authority, wallet, amount, periodIdx) for each period


forceClaimForPeriod(address wallet, uint256 paymentPeriodIdx, uint256 amount)

Force claims interest for a specific period on behalf of a wallet.

Parameters:

  • wallet (address): Address to claim interest for
  • paymentPeriodIdx (uint256): Index of the payment period
  • amount (uint256): Amount to claim

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Period must exist
  • wallet must not be the InterestPayment contract itself, otherwise reverts with InterestPayment_InvalidRecipientAddress

Emits: ForceClaimed(authority, wallet, amount, paymentPeriodIdx)


batchForceClaimInterest(address wallet, uint256[] paymentPeriodIdxs, uint256 amount)

Force claims interest across multiple periods on behalf of a wallet.

Parameters:

  • wallet (address): Address to claim interest for
  • paymentPeriodIdxs (uint256[]): Array of payment period indices
  • amount (uint256): Maximum total amount to claim

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • wallet must not be the InterestPayment contract itself, otherwise reverts with InterestPayment_InvalidRecipientAddress

Emits: ForceClaimed(authority, wallet, amount, periodIdx) for each period


Interest Reclaiming Functions

reclaimInterest(address wallet, uint256 amount)

Reclaims unclaimed interest from a wallet to the reclaimer address.

Parameters:

  • wallet (address): Address to reclaim interest from
  • amount (uint256): Maximum amount to reclaim (0 = reclaim all available)

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Reclaimer address must be set

Emits: Reclaimed(reclaimerAddress, wallet, amount, periodIdx) for each period


reclaimInterestForPeriod(address wallet, uint256 paymentPeriodIdx, uint256 amount)

Reclaims interest from a specific period.

Parameters:

  • wallet (address): Address to reclaim interest from
  • paymentPeriodIdx (uint256): Index of the payment period
  • amount (uint256): Amount to reclaim

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Period must exist
  • Reclaimer address must be set

Emits: Reclaimed(reclaimerAddress, wallet, amount, paymentPeriodIdx)


batchReclaimInterest(address wallet, uint256[] paymentPeriodIdxs, uint256 amount)

Reclaims interest across multiple periods.

Parameters:

  • wallet (address): Address to reclaim interest from
  • paymentPeriodIdxs (uint256[]): Array of payment period indices
  • amount (uint256): Maximum total amount to reclaim

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Reclaimer address must be set

Emits: Reclaimed(reclaimerAddress, wallet, amount, periodIdx) for each period


reclaimInterestForAllRecipients(uint256 paymentPeriodIdx)

Reclaims all remaining interest from a completed period.

Parameters:

  • paymentPeriodIdx (uint256): Index of the payment period

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Period must be completed (past end timestamp)
  • Reclaimer address must be set

Emits: ReclaimedAll(reclaimerAddress, amount, paymentPeriodIdx)


reclaimTotalInterest(uint256 amount)

Reclaims a specified amount from total unused interest funds.

Parameters:

  • amount (uint256): Amount to reclaim

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Amount must be available in unused funds

Emits: ReclaimedAll(reclaimerAddress, amount, type(uint256).max)


Principal Management Functions

fundPrincipal(uint256 amount)

Funds principal amount for maturity claims.

Parameters:

  • amount (uint256): Amount of payment tokens to fund as principal

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • The running funded total (totalAvailablePrincipalAmount() + amount) must not exceed requiredPrincipalFunding(), or the call reverts with InterestPayment_PrincipalFundingExceedsEntitlement(provided, maximum)

Funding target: call requiredPrincipalFunding() and fund up to that value, in one call or in tranches. It equals circulatingBaseUnits × principalAmountPerToken, the sum of every outstanding holder entitlement. Principal claims stay shut until the pool reaches it — see Units of principalAmountPerToken and the Operational notes above.

Emits: PrincipalFunded(funder, amount), and PrincipalFullyFunded(total) on the call that first brings the pool up to the full entitlement


claimPrincipal(uint256 amount)

Claims principal at maturity by burning tokens.

Parameters:

  • amount (uint256): Amount to claim (0 = claim all available)

Requirements:

  • Contract must not be paused
  • Maturity must be reached
  • Caller must have tokens to burn
  • Sufficient principal must be funded

Emits: PrincipalClaimed(claimer, amount)


forceClaimPrincipal(address wallet, uint256 amount)

Force claims principal on behalf of a wallet.

Parameters:

  • wallet (address): Address to claim principal for
  • amount (uint256): Amount to claim (0 = claim all available)

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Maturity must be reached
  • wallet must not be the InterestPayment contract itself, otherwise reverts with InterestPayment_InvalidRecipientAddress

Emits: PrincipalClaimed(wallet, amount)


reclaimPrincipal(uint256 amount)

Reclaims unused principal funds.

Parameters:

  • amount (uint256): Amount to reclaim (0 = reclaim all available)

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Reclaimer address must be set

Emits: PrincipalReclaimed(authority, amount)


Dividend Functions (IDividends Interface)

fundDividend(address token_, uint256 amount_, uint256 timestamp_)

Funds dividend distribution for a specific snapshot timestamp.

Parameters:

  • token_ (address): ERC-20 token address for dividend payments
  • amount_ (uint256): Amount of tokens to distribute
  • timestamp_ (uint256): Snapshot timestamp for token holder eligibility

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Timestamp must be in the past
  • Amount must be divisible by total supply at timestamp

Emits: DividendFunded(funder, token_, amount_, timestamp_)


claimDividend(address token_, uint256 timestamp_, uint256 amount)

Claims dividend for a specific token and timestamp.

Parameters:

  • token_ (address): ERC-20 token address
  • timestamp_ (uint256): Snapshot timestamp
  • amount (uint256): Amount of dividend to claim (0 = claim the full available amount)

Requirements:

  • Contract must not be paused
  • Timestamp must be valid (past timestamp)
  • Must have unclaimed dividend balance
  • Amount of dividend to claim. Must be 0 (to claim the full available amount) or less than or equal to the available unclaimed dividend for the account at the given snapshot.

Emits: DividendClaimed(claimer, token_, amount, timestamp_)


reclaimDividend(address token_, uint256 timestamp_, address account, uint256 amount)

Reclaims unclaimed dividend for a specific account, token, and snapshot timestamp.

Parameters:

  • token_ (address): ERC-20 token address
  • account (address): Address to reclaim dividend from
  • timestamp_ (uint256): Snapshot timestamp
  • amount (uint256): Amount of dividend to reclaim (0 = reclaim the full available amount)

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Reclaimer address must be set
  • Amount must not exceed unclaimed dividend for account

Emits: DividendReclaimed(reclaimer, account, token_, amount, timestamp_)


reclaimTotalDividend(address token_, uint256 timestamp_, uint256 amount)

Reclaims unclaimed dividend for all accounts for a specific token and snapshot timestamp.

Parameters:

  • token_ (address): ERC-20 token address
  • timestamp_ (uint256): Snapshot timestamp
  • amount (uint256): Amount to reclaim (0 = reclaim all available unclaimed for timestamp)

Requirements:

  • Caller must have TRANSFER_ADMIN_ROLE
  • Contract must not be paused
  • Reclaimer address must be set
  • Amount must not exceed total unclaimed dividend for timestamp

Emits: DividendReclaimed(reclaimer, contractAddress, token_, amount, timestamp_)


batchClaimDividend(address token_, uint256[] timestamps_, uint256[] amounts)

Claims dividends across multiple timestamps for a token.

Parameters:

  • token_ (address): ERC-20 token address
  • timestamps_ (uint256[]): Array of snapshot timestamps
  • amounts (uint256[]): Array of amounts to claim for each timestamp (0 = claim full unclaimed amount for that snapshot)

Requirements:

  • Contract must not be paused
  • All timestamps and amounts must be valid
  • The timestamps_ and amounts arrays must have the same length

Emits: DividendClaimed(claimer, token_, amount, timestamp_) for each timestamp


Query Functions

Interest Calculation Functions

accruedInterest(address account) → uint256

Returns total accrued interest for an account up to current time.

Parameters:

  • account (address): Address to query interest for

Returns:

  • uint256: Total accrued interest amount

accruedInterestAt(address account, uint256 timestamp) → uint256

Returns accrued interest for an account up to a specific timestamp.

Parameters:

  • account (address): Address to query interest for
  • timestamp (uint256): Timestamp to calculate interest up to

Returns:

  • uint256: Accrued interest amount at timestamp

accruedInterestForPeriod(address account, uint256 periodIdx) → uint256

Returns accrued interest for a specific period.

Parameters:

  • account (address): Address to query interest for
  • periodIdx (uint256): Payment period index

Returns:

  • uint256: Accrued interest for the period

totalAccruedInterest() → uint256

Returns total accrued interest across all accounts and periods.

Returns:

  • uint256: Total accrued interest amount

totalAccruedInterestAt(uint256 timestamp) → uint256

Returns total accrued interest up to a specific timestamp.

Parameters:

  • timestamp (uint256): Timestamp to calculate interest up to

Returns:

  • uint256: Total accrued interest at timestamp

Period Information Functions

paymentPeriodsCount() → uint256

Returns the number of created payment periods.

Returns:

  • uint256: Count of payment periods

paymentPeriodPaused(uint256 periodIdx) → bool

Checks if a payment period is paused.

Parameters:

  • periodIdx (uint256): Payment period index

Returns:

  • bool: True if period is paused

periodTotalInterest(uint256 periodIdx) → uint256

Returns total interest for a period based on current accrual.

Parameters:

  • periodIdx (uint256): Payment period index

Returns:

  • uint256: Total interest amount for the period

periodAvailableInterest(uint256 periodIdx) → uint256

Returns available (unclaimed/unreclaimed) interest for a period.

Parameters:

  • periodIdx (uint256): Payment period index

Returns:

  • uint256: Available interest amount

periodStartTimestamp(uint256 periodIdx) → uint256

Returns the start timestamp of a payment period.

Parameters:

  • periodIdx (uint256): Payment period index

Returns:

  • uint256: Period start timestamp

periodEndTimestamp(uint256 periodIdx) → uint256

Returns the end timestamp of a payment period.

Parameters:

  • periodIdx (uint256): Payment period index

Returns:

  • uint256: Period end timestamp

periodDuration(uint256 periodIdx) → uint256

Returns the duration of a payment period in seconds.

Parameters:

  • periodIdx (uint256): Payment period index

Returns:

  • uint256: Period duration in seconds

Account Balance Functions

unclaimedAmountForPeriod(address receiver_, uint256 paymentPeriodIdx_) → uint256

Returns unclaimed interest amount for an account in a specific period.

Parameters:

  • receiver_ (address): Address to query
  • paymentPeriodIdx_ (uint256): Payment period index

Returns:

  • uint256: Unclaimed interest amount

claimedAmountForPeriod(address receiver_, uint256 periodIdx_) → uint256

Returns claimed interest amount for an account in a specific period.

Parameters:

  • receiver_ (address): Address to query
  • periodIdx_ (uint256): Payment period index

Returns:

  • uint256: Claimed interest amount

reclaimedAmountForPeriod(address receiver_, uint256 periodIdx_) → uint256

Returns reclaimed interest amount for an account in a specific period.

Parameters:

  • receiver_ (address): Address to query
  • periodIdx_ (uint256): Payment period index

Returns:

  • uint256: Reclaimed interest amount

accountTotalClaimedAmount(address receiver_) → uint256

Returns total claimed interest across all periods for an account.

Parameters:

  • receiver_ (address): Address to query

Returns:

  • uint256: Total claimed amount

accountTotalReclaimedAmount(address receiver_) → uint256

Returns total reclaimed interest across all periods for an account.

Parameters:

  • receiver_ (address): Address to query

Returns:

  • uint256: Total reclaimed amount

Contract State Functions

totalInterestAmountFunded() → uint256

Returns total amount of interest funds deposited.

Returns:

  • uint256: Total funded interest amount

totalInterestAmountUnused() → uint256

Returns total unused (available for claims) interest amount.

Returns:

  • uint256: Total unused interest amount

totalInterestAmountClaimed() → uint256

Returns total amount of interest claimed by users.

Returns:

  • uint256: Total claimed interest amount

totalInterestAmountReclaimed() → uint256

Returns total amount of interest reclaimed by admin.

Returns:

  • uint256: Total reclaimed interest amount

Principal Functions

fundedPrincipalAmount() → uint256

Returns total principal amount funded.

Returns:

  • uint256: Total funded principal amount

claimedPrincipalAmount() → uint256

Returns total principal amount claimed.

Returns:

  • uint256: Total claimed principal amount

reclaimedPrincipalAmount() → uint256

Returns total principal amount reclaimed.

Returns:

  • uint256: Total reclaimed principal amount

totalAvailablePrincipalAmount() → uint256

Returns total available principal amount for claims.

Returns:

  • uint256: Total available principal amount

availablePrincipalAmount(address account) → uint256

Returns available principal amount for a specific account.

Parameters:

  • account (address): Address to query

Returns:

  • uint256: Available principal amount for the account

Contract Configuration Functions

restrictedLockupToken() → address

Returns the RestrictedLockupToken contract address.

Returns:

  • address: Token contract address

paymentToken() → address

Returns the payment token contract address.

Returns:

  • address: Payment token contract address

principalAmountPerToken() → uint256

Returns the principal amount per token for maturity claims.

Returns:

  • uint256: Payment-token base units per base unit of the security token (not per whole token). A holder's entitlement is balanceOf(holder) × principalAmountPerToken, with balanceOf in base units. See Units of principalAmountPerToken for the face-value conversion.

principalAmountPerWholeToken() → uint256

Returns the face value per whole security token implied by principalAmountPerToken(). Convenience view for verifying deployment configuration: a value that is orders of magnitude away from the intended face value means principalAmountPerToken was configured per whole token instead of per base unit.

Returns:

  • uint256: principalAmountPerToken() × 10 ** securityTokenDecimals

requiredPrincipalFunding() → uint256

Returns the payment-token amount required to fully fund principal for the current circulating supply.

Returns:

  • uint256: circulatingBaseUnits × principalAmountPerToken, where circulatingBaseUnits is restrictedLockupToken.totalSupply() - restrictedLockupToken.balanceOf(interestPayment)

Read this before calling fundPrincipal — it is the ceiling on the running funded total, and the figure the pool must reach before principal redemption opens. The value falls as tokens are soft-burned by principal claims, so a fully funded contract stays fully funded through redemption. It rises when new supply is minted, which is what shuts redemption until the pool is topped up.

A supply reduction outside a principal claim can leave the pool above this figure; further funding then reverts until new supply is minted or the excess is reclaimed.

Saturation: if circulatingBaseUnits × principalAmountPerToken would overflow uint256, this returns type(uint256).max rather than reverting, which would otherwise brick principalRedemptionOpen(), availablePrincipalAmount() and fundPrincipal(). That requirement is unreachable, so redemption stays shut and reclaimPrincipal() is the exit.

principalRedemptionOpen() → bool

Whether principal redemption is open, i.e. the pool currently covers requiredPrincipalFunding(). While false, claimPrincipal and forceClaimPrincipal revert with InterestPayment_PrincipalNotFullyFunded and availablePrincipalAmount returns 0.

It does not latch. The predicate is evaluated live on every call and every claim, so it can go back to false: reclaimPrincipal() lowers the pool and newly minted supply raises the entitlement. Re-read it after any mint or reclaim rather than caching the answer — no event marks the pool going short. Leaving redemption open against a short pool would pay the first claimants in full and leave later holders nothing, which is the whole reason the gate exists.

Claims never shut it: a claim soft-burns n base units and removes exactly n × principalAmountPerToken from the pool, lowering the pool and the requirement equally. Because it is evaluated live, coverage reached without a funding call also opens redemption — which matters when supply shrinks outside a principal claim while redemption is still shut, since requiredPrincipalFunding() then drops below the funded total and every fundPrincipal call reverts as an overshoot.

It also reads false in two states where no claim could succeed anyway — before any supply has been minted, and once every token has been redeemed. requiredPrincipalFunding() is 0 in both, so a false here at the end of a completed redemption is expected, not a fault.


Utility Functions

nearestInterestPaymentTimestampAt(uint256 timestamp) → uint256

Returns the nearest valid interest payment timestamp.

Parameters:

  • timestamp (uint256): Input timestamp

Returns:

  • uint256: Nearest payment timestamp

findPaymentPeriodIndex(uint256 startTimestamp, uint256 endTimestamp) → uint256

Finds the index of a payment period with specific start/end timestamps.

Parameters:

  • startTimestamp (uint256): Period start timestamp
  • endTimestamp (uint256): Period end timestamp

Returns:

  • uint256: Period index or type(uint256).max if not found

Dividend Query Functions

totalAwardedBalanceAt(address token_, address receiver_, uint256 timestamp_) → uint256

Returns total awarded dividend balance (claimed + unclaimed) for an account.

Parameters:

  • token_ (address): Dividend token address
  • receiver_ (address): Address to query
  • timestamp_ (uint256): Snapshot timestamp

Returns:

  • uint256: Total awarded balance

unclaimedBalanceAt(address token_, address receiver_, uint256 timestamp_) → uint256

Returns unclaimed dividend balance for an account at a timestamp.

Parameters:

  • token_ (address): Dividend token address
  • receiver_ (address): Address to query
  • timestamp_ (uint256): Snapshot timestamp

Returns:

  • uint256: Unclaimed dividend balance

claimedBalanceAt(address token_, address receiver_, uint256 timestamp_) → uint256

Returns claimed dividend balance for an account at a timestamp.

Parameters:

  • token_ (address): Dividend token address
  • receiver_ (address): Address to query
  • timestamp_ (uint256): Snapshot timestamp

Returns:

  • uint256: Claimed dividend balance

tokensAt(address token_, uint256 timestamp_) → uint256

Returns unused dividend tokens available for claims at a timestamp.

Parameters:

  • token_ (address): Dividend token address
  • timestamp_ (uint256): Snapshot timestamp

Returns:

  • uint256: Unused dividend token amount

fundsAt(address token_, uint256 timestamp_) → uint256

Returns total funded dividend amount for a token at a timestamp.

Parameters:

  • token_ (address): Dividend token address
  • timestamp_ (uint256): Snapshot timestamp

Returns:

  • uint256: Total funded dividend amount

totalSupplyAt(uint256 timestamp_) → uint256

Returns total token supply at a specific timestamp.

Parameters:

  • timestamp_ (uint256): Snapshot timestamp

Returns:

  • uint256: Total supply at timestamp

balanceOfAt(address sender_, uint256 timestamp_) → uint256

Returns token balance of an address at a specific timestamp.

Parameters:

  • sender_ (address): Address to query
  • timestamp_ (uint256): Snapshot timestamp

Returns:

  • uint256: Balance at timestamp

isAmountDivisible(uint256 amount_, uint256 timestamp_) → bool

Checks if an amount is divisible by total supply at a timestamp.

Parameters:

  • amount_ (uint256): Amount to check
  • timestamp_ (uint256): Snapshot timestamp

Returns:

  • bool: True if amount is divisible by total supply

Events

Interest Payment Events

Funded(address indexed funder, uint256 amount)

Emitted when interest funds are deposited.

Parameters:

  • funder (address, indexed): Address that funded the contract
  • amount (uint256): Amount of tokens funded

Claimed(address indexed claimer, uint256 amount, uint256 indexed periodIdx)

Emitted when interest is claimed by a user.

Parameters:

  • claimer (address, indexed): Address that claimed interest
  • amount (uint256): Amount of interest claimed
  • periodIdx (uint256, indexed): Payment period index

ForceClaimed(address indexed authority, address indexed wallet, uint256 amount, uint256 indexed periodIdx)

Emitted when interest is force claimed by admin.

Parameters:

  • authority (address, indexed): Admin address that performed force claim
  • wallet (address, indexed): Wallet address that received the claim
  • amount (uint256): Amount of interest claimed
  • periodIdx (uint256, indexed): Payment period index

Reclaimed(address indexed reclaimerAddress, address indexed wallet, uint256 amount, uint256 indexed periodIdx)

Emitted when interest is reclaimed from a wallet.

Parameters:

  • reclaimerAddress (address, indexed): Address that received reclaimed funds
  • wallet (address, indexed): Wallet address funds were reclaimed from
  • amount (uint256): Amount of interest reclaimed
  • periodIdx (uint256, indexed): Payment period index

ReclaimedAll(address indexed reclaimerAddress, uint256 amount, uint256 indexed periodIdx)

Emitted when all remaining interest is reclaimed from a period.

Parameters:

  • reclaimerAddress (address, indexed): Address that received reclaimed funds
  • amount (uint256): Amount of interest reclaimed
  • periodIdx (uint256, indexed): Payment period index (type(uint256).max for total reclaim)

Payment Period Events

PaymentPeriodCreated(address indexed authority, uint256 indexed periodIdx, uint256 startTimestamp, uint256 endTimestamp, uint256 interestRate)

Emitted when a new payment period is created.

Parameters:

  • authority (address, indexed): Address that created the period
  • periodIdx (uint256, indexed): Index of the created period
  • startTimestamp (uint256): Period start timestamp
  • endTimestamp (uint256): Period end timestamp
  • interestRate (uint256): Interest rate in basis points

InterestRateUpdated(address indexed authority, uint256 indexed periodIdx, uint256 interestRate)

Emitted when interest rate is updated for a period.

Parameters:

  • authority (address, indexed): Address that updated the rate
  • periodIdx (uint256, indexed): Payment period index
  • interestRate (uint256): New interest rate in basis points

PeriodPaused(address indexed authority, uint256 indexed periodIdx)

Emitted when a payment period is paused.

Parameters:

  • authority (address, indexed): Address that paused the period
  • periodIdx (uint256, indexed): Payment period index

PeriodUnpaused(address indexed authority, uint256 indexed periodIdx)

Emitted when a payment period is unpaused.

Parameters:

  • authority (address, indexed): Address that unpaused the period
  • periodIdx (uint256, indexed): Payment period index

Administrative Events

PaymentPausedAfter(uint256 timestamp)

Emitted when payments are paused after a timestamp.

Parameters:

  • timestamp (uint256): Timestamp after which payments are paused

PaymentUnpausedAfter()

Emitted when payment pause timestamp is removed.


ReclaimerAddressChanged(address indexed authority, address indexed newReclaimerAddress)

Emitted when reclaimer address is changed.

Parameters:

  • authority (address, indexed): Address that changed the reclaimer
  • newReclaimerAddress (address, indexed): New reclaimer address

InterestAccrualEndShifted(address indexed authority, uint256 newInterestAccrualEndTimestamp)

Emitted when interest accrual end timestamp is extended.

Parameters:

  • authority (address, indexed): Address that shifted the end timestamp
  • newInterestAccrualEndTimestamp (uint256): New end timestamp

SetMaxInterestRate(address indexed authority, uint256 maxInterestRate)

Emitted when maximum interest rate is changed.

Parameters:

  • authority (address, indexed): Address that set the max rate
  • maxInterestRate (uint256): New maximum interest rate

EarlyRepayment(address indexed authority, uint256 timestamp)

Emitted when early repayment is triggered.

Parameters:

  • authority (address, indexed): Address that triggered early repayment
  • timestamp (uint256): New maturity timestamp

Principal Events

PrincipalFunded(address indexed funder, uint256 amount)

Emitted when principal is funded.

Parameters:

  • funder (address, indexed): Address that funded principal
  • amount (uint256): Amount of principal funded

PrincipalFullyFunded(uint256 total)

Emitted by the fundPrincipal call that brings the pool up to requiredPrincipalFunding(). It is emitted after PrincipalFunded for the same call.

Fires once per completion, not only the first time: minting new supply or reclaiming from the pool takes it back below the entitlement, and funding it up again emits this again. Nothing marks the pool going short, so principalRedemptionOpen() — not this event — is the source of truth for whether claims can succeed.

Parameters:

  • total (uint256): The unused principal total that reached requiredPrincipalFunding()

PrincipalClaimed(address indexed claimer, uint256 amount)

Emitted when principal is claimed.

Parameters:

  • claimer (address, indexed): Address that claimed principal
  • amount (uint256): Amount of principal claimed

PrincipalReclaimed(address indexed authority, uint256 amount)

Emitted when principal is reclaimed.

Parameters:

  • authority (address, indexed): Address that reclaimed principal
  • amount (uint256): Amount of principal reclaimed

Dividend Events

DividendFunded(address indexed funder, address indexed token, uint256 amount, uint256 indexed timestamp)

Emitted when dividend is funded.

Parameters:

  • funder (address, indexed): Address that funded the dividend
  • token (address, indexed): Dividend token address
  • amount (uint256): Amount of dividend funded
  • timestamp (uint256, indexed): Snapshot timestamp

DividendClaimed(address indexed claimer, address indexed token, uint256 amount, uint256 indexed timestamp)

Emitted when dividend is claimed.

Parameters:

  • claimer (address, indexed): Address that claimed dividend
  • token (address, indexed): Dividend token address
  • amount (uint256): Amount of dividend claimed
  • timestamp (uint256, indexed): Snapshot timestamp

Pausable Events

Paused(address account)

Emitted when contract is paused.

Parameters:

  • account (address): Address that paused the contract

Unpaused(address account)

Emitted when contract is unpaused.

Parameters:

  • account (address): Address that unpaused the contract

Custom Errors

Access Control Errors

EasyAccessControl_DoesNotHaveContractAdminRole(address addr)

Thrown when address lacks CONTRACT_ADMIN_ROLE.

Parameters:

  • addr (address): Address that lacks the role

EasyAccessControl_DoesNotHaveTransferAdminRole(address addr)

Thrown when address lacks TRANSFER_ADMIN_ROLE.

Parameters:

  • addr (address): Address that lacks the role

EasyAccessControl_DoesNotHaveContractOrTransferAdminRole(address addr)

Thrown when address lacks both CONTRACT_ADMIN_ROLE and TRANSFER_ADMIN_ROLE.

Parameters:

  • addr (address): Address that lacks the roles

EasyAccessControl_InvalidZeroAddress()

Thrown when zero address is provided where valid address required.


Configuration Errors (API)

InterestPayment_InvalidRestrictedLockupTokenAddress()

Thrown when invalid token address provided in constructor.


InterestPayment_InvalidPaymentPeriodSeconds()

Thrown when payment period seconds is zero.


InterestPayment_InvalidInterestAccrualStartTimestamp()

Thrown when interest accrual start timestamp is zero.


InterestPayment_InvalidInterestAccrualEndTimestamp()

Thrown when interest accrual end timestamp is zero or invalid.


InterestPayment_InvalidInterestAccrualPeriod()

Thrown when start timestamp >= end timestamp.


InterestPayment_InvalidPrincipalAmountPerToken()

Thrown when principal amount per token is zero.


InterestPayment_PrincipalAmountPerTokenTooLarge(uint256 provided, uint256 maximum)

Thrown when principal amount per token exceeds the maximum allowed value (10^47).

Parameters:

  • provided (uint256): The principal amount per token value that was provided
  • maximum (uint256): The maximum allowed value (MAX_PRINCIPAL_AMOUNT_PER_TOKEN)

InterestPayment_InvalidPaymentToken()

Thrown when payment token address is zero.


InterestPayment_InvalidReclaimerAddress()

Thrown when reclaimer address is zero.


InterestPayment_InvalidTimestamp()

Thrown when invalid timestamp provided.


Payment Period Errors

InterestPayment_InvalidPeriod()

Thrown when referencing invalid period.


InterestPayment_InvalidPeriodIndex()

Thrown when period index is out of bounds.


InterestPayment_StartTimestampGreaterThanEndTimestamp()

Thrown when period start > end timestamp.


InterestPayment_StartTimestampBeforeAccrualStartTimestamp()

Thrown when period starts before overall accrual start.


InterestPayment_EndTimestampGreaterThanAccrualEndTimestamp()

Thrown when period ends after overall accrual end.


InterestPayment_PeriodDurationNotMultipleOfInterestRatePeriod()

Thrown when period duration doesn't align with interest rate period.


InterestPayment_PeriodAlreadyExists()

Thrown when attempting to create duplicate period.


InterestPayment_PeriodNotNext()

Thrown when period doesn't follow sequentially.


InterestPayment_NoPaymentPeriods()

Thrown when no payment periods exist.


InterestPayment_PaymentPeriodPaused(uint256 periodIdx)

Thrown when attempting operation on paused period.

Parameters:

  • periodIdx (uint256): Index of paused period

InterestPayment_PeriodAlreadyStarted()

Thrown when trying to modify started period.


Interest Rate Errors

InterestPayment_InterestRateGreaterThanMaxInterestRate()

Thrown when interest rate exceeds the configured maxInterestRate (when maxInterestRate is not 0).


InterestPayment_InterestRateExceedsAbsoluteMax(uint256 provided, uint256 maximum)

Thrown when interest rate exceeds the absolute maximum (MAX_ABSOLUTE_INTEREST_RATE = 10^11), regardless of maxInterestRate setting. This prevents overflow in interest calculations.

Parameters:

  • provided (uint256): The interest rate value that was provided
  • maximum (uint256): The maximum allowed value (MAX_ABSOLUTE_INTEREST_RATE)

InterestPayment_InterestRateNotChanged()

Thrown when trying to set same interest rate.


InterestPayment_MaxInterestRateNotChanged()

Thrown when trying to set same max interest rate.


InterestPayment_InvalidInterestRatePeriodDuration()

Thrown when interestRatePeriodDuration is not one of the allowed values (360, 365, or 366 days).

Security Note: This validation prevents manipulation of interest rate calculations by restricting the duration parameter to standard year lengths.


Funding and Claiming Errors (API)

InterestPayment_InvalidAmount()

Thrown when amount is zero or invalid.


InterestPayment_TokenSupplyIsZero()

Thrown when token supply is zero during operations.


InterestPayment_PrincipalAmountNotDivisibleByTokenSupply()

Thrown when a partial claimPrincipal amount is not evenly divisible by principalAmountPerToken.


InterestPayment_PrincipalFundingExceedsEntitlement(uint256 provided, uint256 maximum)

Thrown by fundPrincipal when totalAvailablePrincipalAmount() + amount would exceed requiredPrincipalFunding() (principalAmountPerToken × circulatingBaseUnits). Under-funding is allowed — the pool may be filled in tranches — but principal redemption stays shut until it reaches that figure. See Units of principalAmountPerToken.

This also fires when a supply reduction outside a principal claim has left the pool over-funded, in which case any amount at all overshoots. That is a safe state — redemption is open, because the pool more than covers what is outstanding — and it clears when new supply is minted or the excess is reclaimed.

Parameters:

  • provided (uint256): The running total that would result from this call (totalAvailablePrincipalAmount() + amount)
  • maximum (uint256): The ceiling, requiredPrincipalFunding(). The running total may be below it; it may not exceed it

InterestPayment_PrincipalNotFullyFunded(uint256 funded, uint256 required)

Thrown by claimPrincipal and forceClaimPrincipal while the principal pool does not cover requiredPrincipalFunding(). Settling a claim out of a short pool would pay that holder in full at the expense of the rest, so every claim path refuses — see Partial funding is not a haircut.

Also thrown before any supply is minted and after all supply has been redeemed, where required is 0 and no claim could succeed.

Parameters:

  • funded (uint256): The unused principal currently in the pool (totalAvailablePrincipalAmount())
  • required (uint256): The amount the pool must cover, requiredPrincipalFunding()

InterestPayment_InvalidFeeApplied()

Thrown when unexpected fee applied during transfer.


InterestPayment_NoFundsToClaim()

Thrown when no funds available for claiming.


InterestPayment_NotEnoughFundsToClaim()

Thrown when insufficient funds for requested claim.


InterestPayment_InvalidUnclaimedAmount(uint256 claimableFunds, uint256 claimedFunds)

Thrown when unclaimed calculation is invalid.

Parameters:

  • claimableFunds (uint256): Expected claimable amount
  • claimedFunds (uint256): Actual claimed amount

InterestPayment_NotEnoughFundedPrincipal()

Thrown when insufficient principal funded.


InterestPayment_MaturityNotReached()

Thrown when trying to claim principal before maturity.


Pause/Unpause Errors

InterestPayment_PaymentNotPausedAfter()

Thrown when trying to unpause when not paused.


InterestPayment_CannotUnpauseAfterMaturity()

Thrown when trying to unpause after maturity.


InterestPayment_CannotReclaimAllForOngoingPeriod()

Thrown when trying to reclaim all from active period.


Dividend Errors

InterestPayment_InvalidDividendSnapshotId()

Thrown when dividend snapshot timestamp is invalid.


InterestPayment_NoRemainingUnclaimedDividendBalance()

Thrown when no unclaimed dividend balance exists.


InterestPayment_InvalidDividendTokenAddress()

Thrown when dividend token address is zero.


InterestPayment_InvalidTokenDecimals(uint8 restrictedLockupTokenDecimals)

Thrown by fundDividend when the security token's decimals exceed the dividend token's decimals. The constructor's payment-token check throws InterestPayment_IncompatibleTokenDecimals instead, so the two are distinguishable off-chain.

Parameters:

  • restrictedLockupTokenDecimals (uint8): RestrictedLockupToken decimals

InterestPayment_IncompatibleTokenDecimals(uint8 securityTokenDecimals, uint8 paymentTokenDecimals)

Thrown by the constructor when the security token's decimals() exceeds the payment token's, since no integer principalAmountPerToken could express an ordinary per-whole-token face value in that pairing.

Parameters:

  • securityTokenDecimals (uint8): The security token's decimals
  • paymentTokenDecimals (uint8): The payment token's decimals

InterestPayment_IndivisibleAmount(uint256 paymentTokenAmount, uint256 totalSupplyAt)

Thrown when dividend amount not divisible by total supply.

Parameters:

  • paymentTokenAmount (uint256): Dividend amount
  • totalSupplyAt (uint256): Total supply at timestamp

InterestPayment_InvalidUnclaimedDividendBalance(uint256 claimableFunds, uint256 claimedFunds)

Thrown when unclaimed dividend calculation is invalid.

Parameters:

  • claimableFunds (uint256): Expected claimable dividend amount
  • claimedFunds (uint256): Actual claimed dividend amount

InterestPayment_ZeroTotalSupply()

Thrown when total supply is zero at snapshot timestamp.


Pausable Errors

EnforcedPause()

Thrown when operation attempted while contract paused.


ExpectedPause()

Thrown when pause expected but contract not paused.