Skip to main content

Borrow Midnight Renewal Callback

BorrowMidnightRenewalCallback migrates a borrow position from a source Morpho Midnight market (for example, one maturing in 2 days) to a target Morpho Midnight market with a later maturity (for example, 30 days), in a single transaction.

Description

Both markets sit on Morpho Midnight. The borrower posts a sell (borrow) offer in the target market (the one maturing in 30 days in the example above) with the callback set as the proceeds receiver. When that sell (borrow) offer is filled, the callback uses the loan-token proceeds to repay the source debt (the position maturing in 2 days) and moves shared collateral from the source market to the target market.

Step-by-step callback execution

Before any take, the borrower posts a renewal sell (borrow) offer on the target market with receiverIfMakerIsSeller set to this callback. The offer carries the callback wiring; the take triggers it.

BorrowMidnightRenewalCallback flow: a taker calls take() on the target market, which routes sellerAssets to the callback, repays the source debt, and moves collateral from the source market to the target market.
  1. Take: A taker fills the borrower's renewal sell (borrow) offer on the target market by calling MORPHO_MIDNIGHT.take(). The loan-token proceeds (sellerAssets) land on the callback because the offer's receiverIfMakerIsSeller points at it, and Morpho Midnight then invokes onSell on the callback.
  2. Validation: The callback checks that source and target markets share the same loanToken. Collaterals listed in both markets are auto-detected and will migrate; collaterals listed only on the source are skipped and stay on the source.
  3. Fee: A fee is derived from the offer tick (an effective-price fee applied to the interest portion only). When the callback is routed through the Migration Ratifier, the ratifier sets the fee rate. Used directly, the offer creator encodes the fee themselves.
  4. Repay source debt: The callback repays the source obligation with sellerAssets - fee. It reads the borrower's current source debt first and reverts with ExcessRepayment if the encoded units would overpay. It also reverts with ZeroAmount if the source debt is already zero.
  5. Move collateral: For each shared collateral token, the callback withdraws pro-rata from the source market (scaled by repaidUnits / sourceDebtBefore) and supplies it on the target market. On the final fill it moves the full remaining balance to avoid leaving dust. The final fill is detected by exact equality (repaidUnits == sourceDebtBefore); with a nonzero fee, fills sized to the remaining offer capacity may never hit that exact equality, leaving residual debt and collateral on the source market until an exact-repayment fill occurs.

Example

A borrower holds a borrow position on cbBTC/USDC · 2d (the source): they owe 10,000 USDC against 0.25 cbBTC of collateral, and that market matures in 2 days. They want to roll it into cbBTC/USDC · 30d (the target) without unwinding and reopening manually.

  1. The borrower posts a sell (borrow) offer for 10,000 USDC on cbBTC/USDC · 30d at 6%, with receiverIfMakerIsSeller set to BorrowMidnightRenewalCallback.
  2. A lender takes the offer. The 10,000 USDC of sellerAssets lands on the callback, and Midnight invokes onSell.
  3. The callback confirms both markets use USDC as loanToken and that cbBTC is listed on both, so it migrates.
  4. It repays the 10,000 USDC owed on cbBTC/USDC · 2d with sellerAssets − fee (the fee is the interest-portion fee derived from the offer's tick).
  5. It withdraws the 0.25 cbBTC from cbBTC/USDC · 2d and supplies it on cbBTC/USDC · 30d.

The borrower ends the take owing ~10,000 USDC on cbBTC/USDC · 30d, backed by the same 0.25 cbBTC, with the 2-day position fully closed.

Prerequisites

Before the take can succeed, the borrower must:

  • Authorize the callback. A single authorization covers both source and target markets, since they live on the same Midnight instance.
  • Post a sell (borrow) offer on the target market with receiverIfMakerIsSeller set to this callback's address.
Source-only collaterals stay behind

Collaterals listed on the source but not on the target will not be migrated. After the renewal, the borrower can call withdrawCollateral on the source market to recover collateral assets in the source market that were not accepted in the target market.

Vault-share collateral requires an allowlist entry

When the migrated collateral is ERC-4626 vault shares behind a Vault V2 Allowlist Gate, this callback must be allowlisted for canReceiveShares. The callback receives the shares transiently while withdrawing them from the source market (step 5), so without the entry the renewal take reverts with CannotReceiveShares. See the allowlist gate page.

Routing

The natural taker of a borrower's target sell (borrow) renewal offer is a lender, since taking that offer means providing loan tokens at the offered rate. A keeper can also match the offer against an existing target lend offer in the RFQ if that results in a positive spread (MEV profit).

When posting a renewal offer, the borrower prices it as the implied combined rate of closing the source position at par early plus borrowing in the target market, not as a standalone target borrow rate. The X% on the target sell (borrow) offer reflects that combined effective rate.

Taker net rate (lender)

When a lender takes the borrower's target sell (borrow) renewal offer, they lend at the offered rate X%. If the lender already holds a position in the source market, or can enter a source-side lend position at a rate above par (0%) in the same transaction, they can capture an extra source-side return: the callback repays the borrower's source debt at par, letting the lender exit the source position atomically and pocket the source-side rate on top.

Example. Borrower's target sell (borrow) offer at X% = 6%. A lender takes the offer and lends at 6% on the target. In the same transaction, the lender also exits a source-side lend position at 5%, atomically unlocked when the callback repays the source debt at par. The lender's net effective rate is 6% on the target plus the source-side profit on top.

Keeper

A third-party keeper doesn't want to hold lend exposure; they match existing offers when a positive net spread exists.

  1. Read the borrower's target sell (borrow) offer at X%.
  2. Find a target buy (lend) offer at Y% and compute the target spread X − Y.
  3. Optionally take a source-side sell (borrow) offer at a positive rate S% in the same transaction: the keeper enters a source-side lend position, and when the callback repays the borrower's source debt at par, that position settles instantly at its full term value, locking in S% of source-side interest for the keeper.
  4. Match the target offers via MORPHO_MIDNIGHT.take() whenever the target spread plus any source-side profit covers gas.

Even when the target leg alone is unprofitable (X − Y ≤ 0 or below gas), a positive source-side leg can cover the shortfall and make the trade profitable overall.

In the Migration Ratifier context, the borrower has posted the renewal as their own maker offer with the ratifier; a keeper fills it permissionlessly, matching it against a buy (lend) offer in the RFQ as soon as the combined spread exceeds gas.

Example. Borrower's target sell (borrow) offer at X% = 6% and a matching target buy (lend) offer at Y% = 2%. The 4% gross spread is the keeper's profit headroom. The keeper matches both offers atomically: the borrower rolls into the 6% target borrow, the lender lends at 2%, and the keeper pockets whatever remains of the 4% spread after gas. If the target legs alone don't clear gas, the keeper can also take a source-side sell (borrow) offer at e.g. S% = 3%; the callback repays the source debt at par, settling the keeper's source position at full term value and adding 3% of interest to the bottom line.