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
],
});