Docs

Guide: How to Monetize Your Midnight Product

Review monetization patterns for Midnight products, including where fees can be applied and the tradeoffs for fixed-rate integrations.

Products built on Midnight can monetize the integration layer while the protocol remains permissionless. A referral fee is a one-time payment in the loan token to an address the product chooses, made in the same transaction as the take.

This guide explains how referral fees relate to protocol fees, how MidnightBundlesV2 calculates them, and how to implement and disclose the fee.

The examples assume a Base viem wallet client walletClient, a Morpho SDK client extended with morphoViemExtension() and viem publicActions, and market/account values supplied by the integration.

Protocol fees versus your fee

Protocol fees and referral fees serve different purposes. Protocol fees follow the market's fee parameters, while your product sets the referral fee.

FeeWho sets itWho pays itWho receives it
SettlementSet per market within protocol-wide capsThe taker, through the settlement spreadThe protocol
ContinuousSet per market within protocol-wide capsLenders, on outstanding creditThe protocol
ReferralYour product, when it calls a MidnightBundlesV2 taker entrypointThe user taking offers, in the loan tokenThe address your product chooses

See Midnight protocol fees for the protocol fee rules. The referral fee is on top of the settlement fee. A user of your product pays both when both are set.

How the referral fee works

Configure both fee parameters and make them visible before the user signs.

MidnightBundlesV2 takes offers, applies the fee to the loan-token leg, transfers the fee to the recipient and the remainder to the user in one transaction. No custom router or fee contract is needed.

  • referralFeePct is a WAD-scaled percentage.
  • referralFeeRecipient is the address that receives the referral fee.

Pass 0 for no fee. The recipient is used only when the calculated fee is positive. The Morpho SDK also requires a recipient when the percentage is positive.

On Midnight, a user authorizes MidnightBundlesV2 once so it can take offers on the user's behalf. This authorization is required whether the integration charges a referral fee or not.

How MidnightBundlesV2 computes the referral fee

The Morpho SDK 7.0.0 take methods call the same entrypoints used by direct contract calls, so these fee formulas apply to both.

FlowEntrypoint(s)Fee formulaMorpho SDK 7.0.0 method(s)Effect on the take
Borrow an exact loan-token amountmidnightBundlesV2SupplyCollateralAndSellWithAssetsTargetfee = targetSellerAssets * pct / (WAD - pct)takeBorrow, supplyCollateralTakeBorrowThe borrower receives the exact target; the fee increases the assets taken and debt at maturity.
Borrow exact unitsmidnightBundlesV2SupplyCollateralAndSellWithUnitsTargetfee = filledSellerAssets * pct / WADtakeBorrow, supplyCollateralTakeBorrowThe units taken are fixed; minSellerAssets bounds the net loan-token amount received.
Lend an exact amountmidnightBundlesV2BuyWithAssetsTargetAndWithdrawCollateralfee = targetBuyerAssets * pct / WADtakeLendThe fee is deducted from the loan-token amount available to take offers.
Lend exact unitsmidnightBundlesV2BuyWithUnitsTargetAndWithdrawCollateralfee = filledBuyerAssets * pct / (WAD - pct)takeLendThe requested units are taken; maxBuyerAssets must cover the assets and fee.
Exit a lend early (reduce-only sell)midnightBundlesV2SupplyCollateralAndSellWithUnitsTargetfee = filledSellerAssets * pct / WADtakeWithdrawThe lender gives up credit units for loan tokens now; the recipient receives the net amount.
Repay or exit a borrow early (reduce-only buy)midnightBundlesV2BuyWithAssetsTargetAndWithdrawCollateral, midnightBundlesV2BuyWithUnitsTargetAndWithdrawCollateralAssets target: fee = targetBuyerAssets * pct / WAD; units target: fee = filledBuyerAssets * pct / (WAD - pct)takeRepayWithdrawCollateralThe fee is included in the loan-token bound as the take reduces debt; collateral can also be withdrawn.
Cancel offers and publish offersmidnightBundlesV2CancelAndMakeNo referral-fee parameterscancelOffers, makeLend, makeBorrow, makeWithdraw, makeRepay, supplyCollateralMakeBorrow, supplyBlueMakeLendMaker publication and cancellation do not accept a referral fee.

All four taker entrypoints reject referralFeePct >= WAD (1e18), but they do not impose a lower product-specific cap. Fee calculations round down, and the recipient receives a transfer only when the calculated fee is positive. All MidnightBundlesV2 entrypoints require a deadline; Morpho SDK 7.0.0 entity take methods limit it to one hour from now.

The target determines the fee's effect. maxBuyerAssets bounds a buyer-units target, minSellerAssets bounds a seller-units target, minUnits bounds a buyer-assets target, and maxUnits bounds a seller-assets target.

Morpho SDK 7.0.0 take actions do not automatically adjust quote amounts for the referral fee. Build the quote from the amount MidnightBundlesV2 actually takes: target plus fee for an exact-amount borrow, and target minus fee for an exact-amount lend. For units-target flows, quote the requested units and derive the gross maxBuyerAssets or net minSellerAssets bound from the corresponding fee formula. The SDK exports grossFromNetAssets, but take actions do not call it. Contract fee calculations round down.

For a borrower to receive exactly 10,000 USDC at a 2% referral fee, the fee is calculated on top of the target:

  • Fee: floor(10,000,000,000 * 20,000,000,000,000,000 / 980,000,000,000,000,000) = 204,081,632 raw units (204.081632 USDC)
  • Amount taken: 10,204,081,632 raw units (10,204.081632 USDC)
  • Borrower receives exactly 10,000 USDC
  • Fee recipient receives 204.081632 USDC

At a price of 0.95 loan-token units per credit unit, taking assets for the fee as well corresponds to about 10,741 units instead of about 10,526. The extra 215 units are debt at maturity that finances the fee. A fee that is 2% of the gross amount is about 2.04% of the 10,000 USDC net target. To express a fee in basis points of the net target, use referralFeePct = feeBps * WAD / (10_000 + feeBps).

Implementation

Morpho SDK

The Morpho SDK 7.0.0 builds take transactions and exposes the same referral-fee parameters as the contract. The transaction must be sent by the account named in accountAddress.

The SDK accepts referralFeePct and referralFeeRecipient on takeLend, takeBorrow, supplyCollateralTakeBorrow, takeWithdraw, and takeRepayWithdrawCollateral. The separate repayWithdrawCollateral method and maker methods do not expose these referral-fee fields. accountAddress must match the transaction sender because MidnightBundlesV2 acts for msg.sender.

import { type Address, parseUnits, publicActions } from "viem";
import { isRequirementSignature, morphoViemExtension } from "@morpho-org/morpho-sdk";
import { MidnightApi } from "@morpho-org/morpho-sdk/midnight-api";

const client = walletClient.extend(morphoViemExtension()).extend(publicActions);
const WAD = 10n ** 18n;
const referralFeePct = parseUnits("0.02", 18);
const referralFeeRecipient = "0x1111111111111111111111111111111111111111" as Address;
const targetSellerAssets = parseUnits("10000", 6);
const fee = (targetSellerAssets * referralFeePct) / (WAD - referralFeePct);
const grossSellerAssets = targetSellerAssets + fee;
const quote = await MidnightApi.fetchBookQuote({
  marketId,
  side: "bids",
  assets: grossSellerAssets,
  slippage: "0.5",
});
const averageWorstPrice = quote.data.averageWorstPrice;
if (!averageWorstPrice) throw new Error("No bids available for this quote");
const worstPrice = BigInt(averageWorstPrice);
const maxUnits =
  (grossSellerAssets * WAD + worstPrice - 1n) / worstPrice;
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
const midnight = client.morpho.midnight(chainId);
const output = midnight.takeBorrow({
  accountAddress: user,
  marketData,
  target: { type: "assets", assets: targetSellerAssets, maxUnits },
  takeableOffers: quote.data.takeableOffers,
  referralFeePct,
  referralFeeRecipient,
  deadline,
});

for (const requirement of await output.getRequirements()) {
  if (isRequirementSignature(requirement)) {
    throw new Error("Unexpected signature requirement for a Midnight taker");
  }
  const hash = await client.sendTransaction(requirement);
  await client.waitForTransactionReceipt({ hash });
}

const hash = await client.sendTransaction(output.buildTx());
await client.waitForTransactionReceipt({ hash });

Direct contract call

To borrow an exact loan-token amount, first gross up the target by the fee. Quote that gross amount against bids, then calculate a unit bound from the quote's worst price.

import { type Address, parseUnits, publicActions, zeroAddress } from "viem";
import { morphoViemExtension } from "@morpho-org/morpho-sdk";
import { MidnightApi } from "@morpho-org/morpho-sdk/midnight-api";
import { midnightBundlesV2Abi } from "@morpho-org/morpho-sdk/abis";

const client = walletClient.extend(morphoViemExtension()).extend(publicActions);
const MIDNIGHT_BUNDLES_V2 = "0x0bF30B530f816a619C3F28a3b9f44C441122555D";
// MidnightBundlesV2 on Base (from the @morpho-org/morpho-ts address registry)
const WAD = 10n ** 18n;
const referralFeePct = parseUnits("0.02", 18);
const referralFeeRecipient = "0x1111111111111111111111111111111111111111" as Address;
const targetSellerAssets = parseUnits("10000", 6);
const fee = (targetSellerAssets * referralFeePct) / (WAD - referralFeePct);
const grossSellerAssets = targetSellerAssets + fee;
const quote = await MidnightApi.fetchBookQuote({
  marketId,
  side: "bids",
  assets: grossSellerAssets,
  slippage: "0.5",
});
const averageWorstPrice = quote.data.averageWorstPrice;
if (!averageWorstPrice) throw new Error("No bids available for this quote");
const worstPrice = BigInt(averageWorstPrice);
const maxUnits =
  (grossSellerAssets * WAD + worstPrice - 1n) / worstPrice;
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);

const hash = await client.writeContract({
  account: user,
  address: MIDNIGHT_BUNDLES_V2,
  abi: midnightBundlesV2Abi,
  value: 0n,
  functionName: "midnightBundlesV2SupplyCollateralAndSellWithAssetsTarget",
  args: [
    market, // market
    targetSellerAssets, // exact loan-token amount the borrower receives
    maxUnits, // units bound from the worst quoted price
    false, // reduceOnly
    user, // receiver
    collateralSupplies, // collateral supplied with the borrow
    quote.data.takeableOffers, // offers quoted for grossSellerAssets
    referralFeePct, // referralFeePct
    referralFeeRecipient, // referralFeeRecipient
    deadline, // deadline
    zeroAddress, // wrappedNative, unused when msg.value is 0
  ],
});

The same referralFeePct and referralFeeRecipient parameters apply to the other taker entrypoints. Use the formula for the entrypoint and target type you call.

Production checklist

The fee is plain calldata the user signs. Make it explicit and verifiable.

User disclosure

Before the user signs, disclose:

  • the net loan-token amount the user will receive or spend
  • the referral-fee percentage, amount, and recipient
  • the units taken, which determine debt or credit at maturity
  • the effective fixed rate after the referral and settlement fees
  • the resulting LTV or health factor for a borrow
  • the exact amount due at maturity, including any repay-side fee

Do not present the net amount as the debt. On a borrow, the debt is the units taken, and the fee increases them.

Simulation and guards

Simulate with current market and offer data. Quote gross borrow assets or net lend assets for amount-target flows, and derive maxUnits, minUnits, minSellerAssets, and maxBuyerAssets from the selected target and fee formula. Check collateral health against the gross debt, account for downward rounding, and confirm the recipient can receive the loan token. maxContinuousFee applies only to the two buy entrypoints and the SDK methods that accept it: takeLend and takeRepayWithdrawCollateral. MidnightBundlesV2 checks that a deadline has not passed; Morpho SDK 7.0.0 entity take methods limit deadlines to one hour ahead.

Fee parameters and authorization

Pin the fee configuration and reject any amount above the cap you publish for your product. MidnightBundlesV2 has no taker parameter; it acts for msg.sender. In Morpho SDK 7.0.0, getRequirements() returns any required token approval and Midnight authorization transactions, and accountAddress must match the transaction sender.

Midnight's setIsAuthorized permission is persistent and cannot be scoped by market, action, amount, fee, or deadline. Authorize a limiting smart contract when narrower permissions are needed. Otherwise, disclose the permission's scope and provide a clear way to revoke it.

There is no separate referral-fee repay entrypoint. A buy with repayEnabled can repay remaining debt; takeRepayWithdrawCollateral is a reduce-only buy with optional collateral withdrawals; repayWithdrawCollateral repays directly without referral-fee parameters.

Important limitations

A referral fee does not make a market permissioned. Users can call Midnight or MidnightBundlesV2 directly, or use another integration, and bypass the fee. The fee monetizes your product flow, not the protocol.

Charge a fee when users receive clear value from your product, such as distribution, onboarding, rate discovery, risk tooling, account abstraction, sponsored gas, or portfolio tracking.