CT extends InterestManagerCT and FiraERC20. Like BT, the FW, BT, factory, expiry, and doCacheIndexSameBlock fields are all immutable. All external mutating functions use nonReentrant.
Minting BT+CT
mintBC(receiverBT, receiverCT) takes FW that has been pre-transferred to the CT contract, converts the FW amount to underlying asset units via the BC index, and mints equal BT and CT. The BC index is max(FW.exchangeRate(), storedIndex) — monotonically non-decreasing.
mintBCMulti does the same for multiple receivers in one call.
Redeeming BT+CT
redeemBC(receiver) burns BT from address(this) (must be pre-transferred) and burns CT if not expired. FW is returned based on the current BC index.
Post-expiry: The redeemable FW amount is calculated at the current BC index, but the excess between the post-expiry first index and the current index accrues as treasury interest. Post-expiry yield goes to the protocol, not to redeemers.
Interest accounting
InterestManagerCT tracks per-user interest via UserInterest { index, accrued }. On every CT transfer (_beforeTokenTransfer), interest is distributed to both sender and receiver:
Claims go through _doTransferOutInterest, which deducts interestFeeRate and sends the fee to treasury.
Key design decisions
Interest is based on CT balance, not FW balance
Protocol fees are taken at claim time, not at accrual time
_distributeInterestForTwo skips address(0) and address(this) to avoid phantom accounting
BC index caching
If doCacheIndexSameBlock is true, the BC index is only updated once per block. This prevents sandwich attacks where the FW exchange rate is manipulated within a block to extract value from CT holders.
Post-expiry state
_setPostExpiryData() is called on the first interaction after expiry. It snapshots the BC index and reward indices. After this, the interest index is frozen to postExpiry.firstBCIndex, and all subsequent yield flows to treasury.
redeemInterestAndRewardsPostExpiryForTreasury() sweeps all post-expiry interest to the treasury.