Docs

Exit borrow at maturity

Snippets assume a viem wallet client walletClient for your account on Base. Set marketId to the selected market ID and borrower to walletClient.account.address.

Read outstanding debt & collateral

Fetch the current outstanding debt and locked collateral before preparing the full repayment:

GET https://api.morpho.org/v0/midnight/markets
  /{marketId}/users/{userAddress}/position

// Response
{
  "data": {
    "chain_id":           8453,
    "market_id":          "0xd92de5e7fbb...7614",                // market the borrower borrowed from
    "user_address":       "0xYourWalletAddress",
    "last_indexed_block": "47457420",
    "loan_token":         "0x8335...2913",                       // USDC
    "maturity":           "1798761600",
    "type":               "borrow",                              // "lend" | "borrow" | "collateral_only"
    "credit":             "0",                                   // lender side (0 for a borrower)
    "debt":               "10527000000",                         // outstanding debt to clear
    "pending_fee":        "0",                                   // lender-side fee (0 for a borrower; not part of the debt)
    "last_loss_factor":   "0",
    "loss_factor":        "0",
    "collaterals": [                                             // each entry is { token, amount }
      {
        "token":  "0x4200000000000000000000000000000000000006",  // WETH
        "amount": "5000000000000000000"                          // 5 WETH (18 dec)
      }
    ]
  }
}

Positions with zero credit and debt but non-empty collaterals are returned as type: "collateral_only".

Repay & withdraw collateral

A full collateral withdrawal only succeeds once the position's debt is zero. If accrual leaves even 1 unit unpaid, the withdrawal of the corresponding collateral reverts (it would push the position under its LLTV). A finite approval cap above the indexed debt accounts for accrual before execution.

The Morpho SDK 7.0.0 path uses repayWithdrawCollateral with a full-repayment cap and standard ERC-20 approval. The direct MidnightBundlesV2 call below is the manual equivalent.

Morpho SDK

Extend walletClient and reuse the position read from the previous step:

import { maxUint256, parseUnits, publicActions } from "viem";
import { morphoViemExtension } from "@morpho-org/morpho-sdk";

// Extend the wallet client with the `morpho` namespace and public reads.
const client = walletClient.extend(morphoViemExtension()).extend(publicActions);
const midnight = client.morpho.midnight(8453);
// Fetch market parameters and state from one block.
const block = await client.getBlock();
const marketData = await midnight.getMarketData(marketId, {
  blockNumber: block.number,
});
const position = (
  await midnight.getPositionData({
    marketId,
    accountAddress: borrower,
    parameters: { blockNumber: block.number },
  })
).accrueInterest(block.timestamp);

// Add a small buffer for accrual between the indexed debt read and execution.
const maxBuyerAssets = BigInt(position.debt) + parseUnits("1", 6);
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1 hour from now

const action = midnight.repayWithdrawCollateral({
  accountAddress: borrower,
  marketData,
  repay: { type: "full", maxBuyerAssets },
  collateralWithdrawals: [{ collateralIndex: 0n, assets: maxUint256 }],
  collateralReceiver: borrower,
  deadline,
});

// Fulfill any required USDC approval and MidnightBundlesV2 authorization separately.
for (const requirement of await action.getRequirements({
  approvalAmount: maxBuyerAssets,
})) {
  const requirementHash = await client.sendTransaction({
    account: borrower,
    to: requirement.to,
    data: requirement.data,
    value: requirement.value,
  });
  await client.waitForTransactionReceipt({ hash: requirementHash });
}

const tx = action.buildTx();
const hash = await client.sendTransaction({
  account: borrower,
  to: tx.to,
  data: tx.data,
  value: tx.value,
});
console.log("Repay + withdraw tx:", hash);

See Position maintenance for the full surface, including redeem and cancelOffers.

Raw contract call: Authorizations & Approvals

Submit the USDC ERC-20 approval to MidnightBundlesV2 separately, then authorize it on the Midnight contract.

import { erc20Abi, maxUint256, parseUnits } from "viem";
import { midnightAbi } from "@morpho-org/morpho-sdk/abis";

const MIDNIGHT_BUNDLES_V2 = "0x0bF30B530f816a619C3F28a3b9f44C441122555D"; // MidnightBundlesV2 on Base (from the @morpho-org/morpho-ts address registry)
const MIDNIGHT = "0xAdedD8ab6dE832766Fedf0FaC4992E5C4D3EA18A";
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const maxBuyerAssets = BigInt(position.debt) + parseUnits("1", 6);

await walletClient.writeContract({
  account: borrower,
  address: USDC,
  abi: erc20Abi,
  functionName: "approve",
  args: [MIDNIGHT_BUNDLES_V2, maxBuyerAssets],
});

await walletClient.writeContract({
  account: borrower,
  address: MIDNIGHT,
  abi: midnightAbi,
  functionName: "setIsAuthorized",
  args: [MIDNIGHT_BUNDLES_V2, true, borrower],
});

Raw contract call: Execute onchain

Call the MidnightBundlesV2 units-target entrypoint with no offers to repay the borrower's full debt, then withdraw the full WETH balance.

import { maxUint256, parseUnits, publicActions, zeroAddress } from "viem";
import { morphoViemExtension } from "@morpho-org/morpho-sdk";
import { midnightBundlesV2Abi } from "@morpho-org/morpho-sdk/abis";

const client = walletClient.extend(morphoViemExtension()).extend(publicActions);
const midnight = client.morpho.midnight(8453);
const block = await client.getBlock();
const marketData = await midnight.getMarketData(marketId, {
  blockNumber: block.number,
});
const market = marketData.params;
const MIDNIGHT_BUNDLES_V2 = "0x0bF30B530f816a619C3F28a3b9f44C441122555D"; // MidnightBundlesV2 on Base (from the @morpho-org/morpho-ts address registry)
const maxBuyerAssets = BigInt(position.debt) + parseUnits("1", 6);
const collateralWithdrawals = [{ collateralIndex: 0n, assets: maxUint256 }];

await walletClient.writeContract({
  account: borrower,
  address: MIDNIGHT_BUNDLES_V2,
  abi: midnightBundlesV2Abi,
  functionName: "midnightBundlesV2BuyWithUnitsTargetAndWithdrawCollateral",
  args: [
    market,                         // market
    maxUint256,                     // targetUnits: whole debt
    maxBuyerAssets,                 // maxBuyerAssets
    true,                           // reduceOnly
    true,                           // repayEnabled
    [],                             // offerFills
    collateralWithdrawals,         // collateralWithdrawals
    borrower,                       // collateralReceiver
    0n,                             // referralFeePct
    zeroAddress,                    // referralFeeRecipient
    maxUint256,                     // maxContinuousFee
    BigInt(Math.floor(Date.now() / 1000) + 3600), // deadline
    zeroAddress,                    // wrappedNative, unused when msg.value is 0
  ],
});