Docs

Midnight periphery

Reference Midnight periphery contracts used around the core fixed-rate lending protocol, with interface details for integrators.

Midnight repository

Periphery and ratifiers

Optional helper contracts are available in the periphery folder:

  • BlueBuyCallback - callback that funds buy offers by withdrawing loan tokens previously supplied to a Morpho Blue market on behalf of the callback contract.
  • EcrecoverAuthorizer - enables signature-based authorization changes after the helper has been authorized on Midnight for the relevant account. Signed changes are submitted through the helper and executed onchain.
  • WhitelistEnterGate - optional access-control contract a market can set as its enter gate; only accounts on a side's list can increase their credit (lend side) or debt (borrow side) in that market, and no account is prevented from exiting. Each side has its own role setter, who grants the whitelister role, and whitelisters update the list directly or by EIP-712 signature; a side can be set open at deployment, which lets any account enter on that side permanently.
  • WhitelistEnterGateFactory - deploys WhitelistEnterGate contracts at deterministic addresses (CREATE2) and records them in isWhitelistEnterGate. Addresses are on the Addresses page.

A ratifier decides whether an offer can be taken. Each offer names a ratifier, and when a taker calls take, Midnight asks that ratifier to confirm the maker approved the offer, then rejects the take if it does not.

Ratifier contracts are available in the ratifiers folder.

  • PriceRatifierV1 - verifies that an offer belongs to a Merkle root ratified by its maker, and optionally restricts who can take it. See the reference below.
  • RateRatifierV1 - same as PriceRatifierV1, but the maker commits to a rate instead of a price. See the reference below.
  • SetterRatifier (legacy) - verifies that an offer belongs to a Merkle root ratified by its maker.
  • EcrecoverRatifier (legacy) - verifies that an offer belongs to a Merkle root signed (EIP-712) by its maker or by a signer the maker authorized on Midnight. The maker, or an account it authorized, can cancel a root with cancelRoot.

Morpho SDK 7.0.0 makes offers only with PriceRatifierV1 or RateRatifierV1 trees. SetterRatifier and EcrecoverRatifier stay deployed, and @morpho-org/midnight-sdk still exports their utilities.

Deployment addresses are listed on the Addresses page.

PriceRatifierV1 and RateRatifierV1

Both ratifiers let a maker approve a whole Merkle tree of offers with a single root ratification. All offers in a tree are expected to share the same maker and ratifier.

When an offer is taken, Midnight calls the ratifier's isRatified, which checks that:

  1. the taker is the offer's allowed taker, when one is set (address(0) means anyone can take);
  2. for RateRatifierV1 only, the offer's price respects the committed rate;
  3. the offer is a leaf of the tree (Merkle proof);
  4. the maker has ratified the root.

Price and rate commitments

PriceRatifierV1RateRatifierV1
ratifierData decodes to(bytes32 root, uint256 leafIndex, bytes32[] proof, address allowedTaker)(bytes32 root, uint256 leafIndex, bytes32[] proof, uint256 rate, address allowedTaker)
Leaf committed by the makerThe offer, plus allowedTakerThe offer with tick replaced by rate, plus allowedTaker
PriceThe offer's tick, fixed in the leafAny tick whose price is at or below (buy offers) or at or above (sell offers) the price limit derived from rate

rate is a WAD-scaled per-second rate, quoted as simple interest. It does not account for compounding or continuous fees. At take time, RateRatifierV1 converts it into a price limit using the remaining time to maturity:

priceLimit = WAD * WAD / (WAD + rate * timeToMaturity); // rounded down for buy offers, up for sell offers

A price beyond the limit reverts with WorsePrice.

Ratifying a root

function setIsRootRatified(address maker, bytes32 root, bool newIsRootRatified) external returns (bytes32);

Sets whether root is ratified for maker. The caller must be maker or an account authorized by maker on Midnight. Setting it back to false stops every offer in the tree from being taken. This does not consume rootNonce, so a signed ratification with the current nonce can still be submitted afterwards by an authorized caller and ratify the root again.

function setIsRootRatifiedWithSig(
    address maker,
    bytes32 root,
    uint256 height,
    bool newIsRootRatified,
    uint128 nonce,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) external returns (bytes32);

Same as setIsRootRatified, using an EIP-712 signature over the offer tree. The signer and the caller must each be maker or an account authorized by maker on Midnight. The caller check prevents a third party from submitting a signed ratification before or without the maker's batch.

NameTypeDescription
heightuint256Height of the offer tree (at most 20, otherwise the call reverts with TreeTooHigh).
nonceuint128Must equal rootNonce(maker, root), which is shared by all of the maker's signers. A used nonce is accepted only if it does not change the current status, otherwise the call reverts with RatifiedStatusChanged.
deadlineuint256Timestamp after which the signature is rejected.

Both functions return keccak256("morpho.midnight.setIsRootRatifiedSuccess"). The EIP-712 domain contains only the chain id and the ratifier address.

Views

FunctionReturns
isRootRatified(maker, root)Whether root is ratified for maker.
rootNonce(maker, root)The next nonce expected by setIsRootRatifiedWithSig.
ratification(maker, root)Both values above.
MIDNIGHT()The Midnight contract the ratifier works with.
DOMAIN_SEPARATOR()The EIP-712 domain separator.

Events

EventEmitted when
SetIsRootRatified(address indexed caller, address indexed maker, bytes32 indexed root, bool newIsRootRatified)setIsRootRatified is called.
SetIsRootRatifiedWithSig(address caller, address indexed signer, address indexed maker, bytes32 indexed root, uint256 height, bool newIsRootRatified, uint128 signatureNonce, uint128 previousNonce)setIsRootRatifiedWithSig succeeds. It is also emitted when a used nonce is replayed without changing the status (signatureNonce < previousNonce), so not every emission is a state change.

Errors

ErrorReason
UnauthorizedThe caller or signer is neither the maker nor authorized by the maker on Midnight.
DeadlineExpiredThe signature deadline has passed.
InvalidSignatureThe signature does not recover to an address.
InvalidNonceThe nonce is higher than rootNonce(maker, root).
RatifiedStatusChangedA used nonce would change the current status.
UnauthorizedTakerThe taker is not the offer's allowed taker.
WorsePriceRateRatifierV1 only: the offer's price is beyond the rate's price limit.
InvalidProofThe offer is not a leaf of the tree.
NotRatifiedThe maker has not ratified the root.
LeafIndexOutOfRangeDeclared in HashLib: leafIndex does not fit in proof.length bits.
TreeTooHighDeclared in HashLib: height is above 20 in setIsRootRatifiedWithSig.

LeafIndexOutOfRange and TreeTooHigh are not declared in the ratifier interfaces, so an ABI generated from IPriceRatifierV1 or IRateRatifierV1 alone does not decode them.

Authorization and custody

Before its offers can be taken, the maker authorizes the ratifier once on Midnight with setIsAuthorized. Otherwise take reverts with RatifierUnauthorized.

The ratifiers hold no assets and make no token transfers or approvals. They have no owner and cannot be upgraded. They only store the ratification status and nonce per maker and root, and read Midnight's isAuthorized. Token approvals stay on Midnight, which moves the tokens during take.

A wallet that only allows calls to listed contracts, such as an MPC wallet policy, needs the contract that ratifies its roots as an allowed target:

  • Direct calls: the ratifier. Each tree is ratified with a direct call to the ratifier, setIsRootRatified or setIsRootRatifiedWithSig, from the maker or an account the maker authorized on Midnight. That account needs the ratifier on its allowlist whichever function is used. The one-time setIsAuthorized call goes to Midnight.
  • Morpho SDK 7.0.0: MidnightBundlesV2. SDK maker methods publish offers in one MidnightBundlesV2 call that authorizes the ratifier and ratifies the root, so the wallet calls MidnightBundlesV2 instead of the ratifier. The maker first authorizes MidnightBundlesV2 on Midnight.

BlueFallbackRolling

BlueFallbackRolling lets any caller roll an authorized borrower's Midnight debt and collateral into a configured Morpho Blue market during an enabled time window.

BlueFallbackRolling.sol

For how rolling works, see Rolling loans into Morpho Blue.

Functions

FunctionParameters or returnDescription
MIDNIGHT()Returns addressAddress of the Midnight contract used by this deployment.
BLUE()Returns addressAddress of Morpho Blue used by this deployment.
isConfig(address user, bytes32 configId)Returns boolWhether this parameter hash is enabled for the borrower.
setConfig(address user, bytes32 midnightId, bytes32 blueId, uint256 start, uint256 end, uint256 incentiveAtStart, uint256 incentiveAtEnd, uint256 minRollableAssets, bool enabled)No return valueEnables or disables a rolling configuration. The caller must be the user or authorized for the user on Midnight.
roll(Market memory midnightMarket, MarketParams memory blueMarketParams, address user, uint256 start, uint256 end, uint256 incentiveAtStart, uint256 incentiveAtEnd, uint256 minRollableAssets, uint256 assets)No return valueBorrows from the configured Morpho Blue market, repays Midnight debt, moves collateral, and pays the incentive to the caller.
onMorphoSupplyCollateral(uint256 collateralAssets, bytes calldata data)No return valueMorpho Blue callback that performs the borrow, repayment, and Midnight collateral withdrawal.

Events

EventIndexed fieldsOther fields
SetConfiguser: address, midnightId: bytes32, blueId: bytes32caller: address, start: uint256, end: uint256, incentiveAtStart: uint256, incentiveAtEnd: uint256, minRollableAssets: uint256, enabled: bool
Rolluser: address, midnightId: bytes32, blueId: bytes32caller: address, configId: bytes32, debtAssets: uint256 (amount of debt rolled in this call), collateralAssets: uint256, incentiveAssets: uint256

Errors

ErrorRevert condition
UnauthorizedThe setConfig caller is neither the user nor authorized for the user on Midnight.
EndNotAfterStartThe configuration end is not later than its start.
IncentiveTooHighThe end incentive is greater than 100%.
DecreasingIncentiveThe end incentive is lower than the start incentive.
NotConfiguredNo enabled configuration matches the parameters passed to roll.
NotStartedThe roll call is submitted before the configured start.
EndedThe roll call is submitted after the configured end.
LiquidationLockedThe Midnight position is locked inside a take callback.
IncorrectActivatedCollateralThe borrower does not have exactly one activated collateral on Midnight.
InconsistentCollateralTokenThe Morpho Blue collateral token does not match the active Midnight collateral.
InconsistentLoanTokenThe Morpho Blue loan token does not match the Midnight loan token.
RolledAssetsTooLowThe amount is below minRollableAssets and is not the full remaining debt.
NotBlueThe callback is called by an address other than Morpho Blue.