Docs

Variable Rate Market - Blue

Introduction

Variable Rate - Blue Market (Morpho Blue) is Morpho's shared, variable-rate lending market: lenders supply the loan asset, borrowers post collateral and borrow against it, and rates float with utilization.

The Morpho SDK builds every market transaction - supply, supply collateral, borrow, repay, withdraw, Blue-to-Blue refinance, their atomic combinations, and shared-liquidity reallocations - through the same getRequirements / buildTx flow as every other Morpho surface, via client.morpho.blue(marketParams, chainId).

Setup

Build the extended client once, as shown in the Morpho SDK Setup, then construct the market entity below. Every flow on this page reuses client, the market entity, and userAddress - the client's connected account, per the Builder = signer invariant. Dispatch requirements with the loop from The getRequirements flow, whose publicClient also serves the block reads below.

import { type Address, parseUnits } from "viem";
import { mainnet } from "viem/chains";
import { MarketParams } from "@morpho-org/morpho-sdk/entities";

// `client` is the extended wallet client from the Morpho SDK Setup;
// `userAddress` is its connected account.
const userAddress = USER_ADDRESS;

Build a market entity & fetch data

// Construct a Blue Market entity from its `MarketParams`.
// The unique market id is derivable from these five fields.
const market = client.morpho.blue(
  new MarketParams({
    loanToken: "0xLoanToken00000000000000000000000000000000",
    collateralToken: "0xCollateralToken00000000000000000000000000",
    oracle: "0xOracle0000000000000000000000000000000000",
    irm: "0xIrm00000000000000000000000000000000000000",
    lltv: 860000000000000000n, // 86%
  }),
  mainnet.id,
);

// On-chain reads with accrued interest:
const marketData = await market.getMarketData();
const positionData = await market.getPositionData(userAddress);
//   positionData: AccrualPosition with health metrics
//     { borrowAssets, collateral, supplyShares, borrowShares,
//       maxBorrowAssets, ltv, isHealthy, market, ... }

Supply (loan asset)

// Lend the loan asset to the market. Routed through Bundler3 via GeneralAdapter1,
// with a `maxSharePrice` inflation-attack guard computed from `marketData` and
// `slippageTolerance` (WAD-scaled bigint, default 0.03%).
//
// `getRequirements()` returns the ERC-20 approval (or Permit / Permit2 signature)
// for the loan token to GeneralAdapter1 - no Morpho authorization is needed.
const supply = market.supply({
  amount: parseUnits("1000", 6), // 1,000 USDC, for example
  userAddress,
  marketData,
  // nativeAmount: parseUnits("1", 18), // when the loan token is wNative
});

const supplyLoanReqs = await supply.getRequirements();
const supplyLoanTx = supply.buildTx(/* [requirementSignature] */);

Supplying the loan asset credits your supply position on the market (tracked as supplyShares on AccrualPosition). Like vault deposits, native-token wrapping is supported when the loan token is the chain's wNative - the bundler wraps nativeAmount atomically before the supply. slippageTolerance follows the same WAD-scaled bigint convention as vault deposits.

Supply collateral

// Routed through Bundler3 via GeneralAdapter1.
// `getRequirements()` returns the ERC-20 approval (or Permit / Permit2 signature)
// for the collateral token to GeneralAdapter1.
const supplyCollateral = market.supplyCollateral({
  amount: parseUnits("1", 18),
  userAddress,
  // nativeAmount: parseUnits("1", 18) // when collateral is wNative
});

const supplyReqs = await supplyCollateral.getRequirements();
const supplyTx = supplyCollateral.buildTx(/* [requirementSignature] */);

Borrow

// Routed through Bundler3 via `morphoBorrow`. Requires GeneralAdapter1 to be
// authorized on Morpho - `getRequirements()` returns the `setAuthorization`
// transaction if it has not been done yet.
//
// Always pass a **fresh** `positionData` - stale data may cause unexpected
// health-check failures or unexpected slippage protection.
const borrow = market.borrow({
  amount: parseUnits("500", 6), // 500 USDC, for example
  userAddress,
  positionData,
});

const borrowReqs = await borrow.getRequirements();
const borrowTx = borrow.buildTx();

The SDK validates an LLTV buffer against your positionData before building, so you cannot accidentally build a transaction that lands on the wrong side of the liquidation threshold the moment it includes. The buffer is hardcoded at DEFAULT_LLTV_BUFFER = 5000000000000000n (WAD-scaled, i.e. 0.5% below the market's LLTV) and is not user-configurable; a borrow beyond it throws BorrowExceedsSafeLtvError. getRequirements() returns setAuthorization(generalAdapter1, true) if it has not been done yet for the user.

Supply collateral & borrow (atomic)

// Atomic supply-then-borrow in a single bundle. Validates the hardcoded 0.5%
// LLTV buffer so a fresh position is not instantly liquidatable.
//
// `getRequirements()` returns IN PARALLEL:
//   - ERC-20 approval / Permit / Permit2 for the collateral token to GeneralAdapter1
//   - `morpho.setAuthorization(generalAdapter1, true)` if not yet authorized
const supplyCollateralBorrow = market.supplyCollateralBorrow({
  amount: parseUnits("1", 18),
  borrowAmount: parseUnits("500", 6),
  userAddress,
  positionData,
});

const supplyBorrowReqs = await supplyCollateralBorrow.getRequirements();
const supplyBorrowTx = supplyCollateralBorrow.buildTx(/* [requirementSignature] */);

Repay

Two modes - exactly one of amount or shares:

  • amount - partial repay by exact ERC-20 asset amount, optionally supplemented by nativeAmount when the loan token is wNative.
  • shares - full repay by exact share count, immune to interest accrued between quote and inclusion (recommended for "close position" flows).
// Two modes - exactly one of `amount` / `shares`:
//   - `amount`: partial repay by exact asset amount.
//   - `shares`: full repay by exact share count, immune to interest accrued
//               between quote and inclusion (recommended for "close position").
//
// Repay does NOT require Morpho authorization - only an ERC-20 approval (or
// permit) on the loan token to GeneralAdapter1.

// Partial repay by amount
const partialRepay = market.repay({
  amount: parseUnits("100", 6),
  userAddress,
  positionData,
});
const partialRepayTx = partialRepay.buildTx(/* [requirementSignature] */);

// Full repay by shares
const fullRepay = market.repay({
  shares: positionData.borrowShares,
  userAddress,
  positionData,
});

const repayReqs = await fullRepay.getRequirements();
const fullRepayTx = fullRepay.buildTx(/* [requirementSignature] */);

repay does not require Morpho authorization - only an ERC-20 approval (or permit) on the loan token to GeneralAdapter1.

Withdraw (loan asset)

Two modes - exactly one of assets or shares:

  • assets - withdraw an exact loan-asset amount.
  • shares - burn an exact supply-share count, immune to interest accrued between quote and inclusion (recommended for "close position" flows).
// Withdraw your supplied loan assets. Routed through Bundler3 via `morphoWithdraw`,
// with a `minSharePrice` computed from market state and `slippageTolerance`.
//
// Requires GeneralAdapter1 to be authorized on Morpho - `getRequirements()`
// returns the `setAuthorization` transaction (or, with `supportSignature: true`,
// a signable authorization requirement folded into the bundle) if it is missing.
//
// Always pass a **fresh** `positionData` - stale data may cause unexpected
// supply-share calculations.

// Partial withdraw by assets
const withdrawSupply = market.withdraw({
  assets: parseUnits("500", 6),
  userAddress,
  positionData,
});

const withdrawSupplyReqs = await withdrawSupply.getRequirements();
const withdrawSupplyTx = withdrawSupply.buildTx();

// Full close by shares
const closeSupply = market.withdraw({
  shares: positionData.supplyShares,
  userAddress,
  positionData,
});
const closeSupplyTx = closeSupply.buildTx();

Loan-asset withdraw also accepts a receiver (defaults to userAddress) and, like borrow, an optional reallocations array to pull shared liquidity into the market before withdrawing - see Shared liquidity (reallocations).

Withdraw collateral

// Direct call to `morpho.withdrawCollateral()` - no bundler, no GeneralAdapter1.
// `msg.sender` MUST be `onBehalf`, so this is a single self-signed transaction.
// The SDK validates the resulting position health using the LLTV buffer.
const withdrawCollateral = market.withdrawCollateral({
  amount: parseUnits("0.25", 18),
  userAddress,
  positionData,
});
const withdrawCollateralTx = withdrawCollateral.buildTx();

Direct call to morpho.withdrawCollateral() - no bundler, no GeneralAdapter1 authorization needed. The SDK validates position health after the withdrawal against the LLTV buffer.

Repay & withdraw collateral (atomic)

// Atomic repay → withdraw collateral via Bundler3.
// Bundle order is critical: repay FIRST, then withdraw.
//
// `getRequirements()` returns IN PARALLEL:
//   - ERC-20 approval / Permit / Permit2 for the loan token (for repay)
//   - `morpho.setAuthorization(generalAdapter1, true)` if not yet authorized (for withdraw)
//
// The SDK simulates the repay before validating that the resulting position
// can sustain the requested collateral withdrawal.
const repayWithdrawCollateral = market.repayWithdrawCollateral({
  amount: parseUnits("100", 6), // or { shares: ... }
  withdrawAmount: parseUnits("0.25", 18),
  userAddress,
  positionData,
});

const repayWithdrawReqs = await repayWithdrawCollateral.getRequirements();
const repayWithdrawTx = repayWithdrawCollateral.buildTx(/* [requirementSignature] */);

Bundle order is critical and is encoded for you: repay first, then withdraw. The SDK simulates the repay before validating that the resulting position can sustain the requested collateral withdrawal.

Refinance to another Morpho Blue market

refinance atomically migrates a borrower position from a source Morpho Blue market to a target Morpho Blue market that uses the same loanToken and collateralToken.

The target market may differ by oracle, IRM, or LLTV. The SDK validates both markets and builds one Bundler3 transaction that:

  1. supplies collateral on the target market,
  2. borrows on the target market inside the callback,
  3. repays the source market,
  4. withdraws source collateral to settle the deferred collateral transfer.

No user-side prefunding is required for the migrated collateral. getRequirements() only returns the Morpho authorization transaction for GeneralAdapter1 when the user has not authorized it yet.

const sourceMarketParams = new MarketParams({
  loanToken: "0xLoanToken00000000000000000000000000000000",
  collateralToken: "0xCollateralToken00000000000000000000000000",
  oracle: "0xSourceOracle0000000000000000000000000000",
  irm: "0xIrm00000000000000000000000000000000000000",
  lltv: 860000000000000000n,
});

const targetMarketParams = new MarketParams({
  loanToken: "0xLoanToken00000000000000000000000000000000",
  collateralToken: "0xCollateralToken00000000000000000000000000",
  oracle: "0xTargetOracle0000000000000000000000000000",
  irm: "0xIrm00000000000000000000000000000000000000",
  lltv: 915000000000000000n,
});

const sourceMarket = client.morpho.blue(sourceMarketParams, mainnet.id);
const targetMarket = client.morpho.blue(targetMarketParams, mainnet.id);

const sourcePosition = await sourceMarket.getPositionData(userAddress);
const targetPosition = await targetMarket.getPositionData(userAddress);

const refinance = sourceMarket.refinance({
  userAddress,
  positionData: sourcePosition,
  target: {
    marketParams: targetMarketParams,
    positionData: targetPosition,
  },
  collateralAmount: parseUnits("5", 18),
  borrowAssets: parseUnits("5", 18),
});

const refinanceRequirements = await refinance.getRequirements();

// Only the Morpho authorization can come back here. Dispatch it like any other
// requirement: a transaction to send, or - with `supportSignature: true` - a
// signable requirement whose signature is folded into the bundle.
const refinanceSignatures: RequirementSignature[] = [];

for (const requirement of refinanceRequirements) {
  if (isRequirementSignature(requirement)) {
    refinanceSignatures.push(await requirement.sign(client, userAddress));
  } else {
    const hash = await client.sendTransaction(requirement);
    await publicClient.waitForTransactionReceipt({ hash });
  }
}

const refinanceTx = refinance.buildTx(refinanceSignatures);
await client.sendTransaction(refinanceTx);

refinance supports three modes:

  • Assets mode - pass borrowAssets to migrate an exact debt amount.
  • Shares mode - pass borrowShares to migrate an exact source borrow-share amount. This is the preferred full-close mode because it is immune to interest accrual between quote and inclusion.
  • Collateral-only mode - omit both borrowAssets and borrowShares to move collateral only.
// Assets mode: migrate an exact debt amount.
const assetsModeRefinance = sourceMarket.refinance({
  userAddress,
  positionData: sourcePosition,
  target: {
    marketParams: targetMarketParams,
    positionData: targetPosition,
  },
  collateralAmount: parseUnits("5", 18),
  borrowAssets: parseUnits("2", 18),
});

// Shares mode: full-close source debt.
const sharesModeRefinance = sourceMarket.refinance({
  userAddress,
  positionData: sourcePosition,
  target: {
    marketParams: targetMarketParams,
    positionData: targetPosition,
  },
  collateralAmount: sourcePosition.collateral,
  borrowShares: sourcePosition.borrowShares,
});

// Collateral-only mode.
const collateralOnlyRefinance = sourceMarket.refinance({
  userAddress,
  positionData: sourcePosition,
  target: {
    marketParams: targetMarketParams,
    positionData: targetPosition,
  },
  collateralAmount: parseUnits("5", 18),
});

refinance requires Morpho authorization for GeneralAdapter1. It does not need ERC-20 approvals because the migrated collateral and debt are moved through Morpho callbacks, not pulled from the user wallet.

Shared liquidity (reallocations)

When the borrowed market lacks liquidity but a MetaMorpho vault you trust has spare capacity in another market, you can reallocate liquidity via the Public Allocator (see the concept page for how the mechanism works). The SDK encodes a reallocateTo() call per VaultReallocation and prepends them to the bundle, before morphoBorrow.

You can either pass an explicit reallocations array, or let the SDK compute one for you from ReallocationData:

import type { VaultReallocation } from "@morpho-org/morpho-sdk";

// Pass an explicit `reallocations` array when the borrowed market lacks
// liquidity but a MetaMorpho vault you trust has spare capacity in another
// market it allocates to. The SDK encodes a `PublicAllocator.reallocateTo()`
// call for each entry and prepends it to the bundle, BEFORE `morphoBorrow`.
const reallocations: VaultReallocation[] = [
  {
    vault: "0xVaultV1Address0000000000000000000000000000",
    fee: 0n, // PublicAllocator fee in native token (often 0)
    withdrawals: [
      {
        marketParams: {
          loanToken: "0xLoanToken00000000000000000000000000000000",
          collateralToken:
            "0xSourceCollateralToken000000000000000000000",
          oracle: "0xSourceOracle0000000000000000000000000000",
          irm: "0xIrm00000000000000000000000000000000000000",
          lltv: 860000000000000000n,
        },
        amount: parseUnits("2000", 6),
      },
    ],
  },
];

const borrowWithRealloc = market.borrow({
  amount: parseUnits("500", 6),
  userAddress,
  positionData,
  reallocations,
});

const borrowWithReallocTx = borrowWithRealloc.buildTx();
// `borrowWithReallocTx.value` includes the sum of every PublicAllocator fee.
// Or let the SDK compute the optimal reallocation set for you:
//   1. `getReallocationData` - fetch ReallocationData for candidate vaults.
//   2. `getReallocations`    - derive the `VaultReallocation[]` from a target borrow size.
const block = await publicClient.getBlock();

const reallocationData = await market.getReallocationData({
  vaultAddresses: [
    "0xVaultV1Address0000000000000000000000000000",
  ] as Address[],
  block: { number: block.number, timestamp: block.timestamp },
});

const computed = market.getReallocations({
  reallocationData,
  operation: "borrow",
  amount: parseUnits("500", 6),
});

const borrowAuto = market.borrow({
  amount: parseUnits("500", 6),
  userAddress,
  positionData,
  reallocations: computed,
});

const borrowAutoTx = borrowAuto.buildTx();

Reallocations work the same way for supplyCollateralBorrow, and for loan-asset withdraw (compute them with operation: "withdraw"). For refinance, reallocations target the target market because the callback borrows there before repaying the source market. Pass them as targetReallocations. The transaction's value includes the sum of every PublicAllocator fee.

const block = await publicClient.getBlock();

const targetReallocationData = await targetMarket.getReallocationData({
  vaultAddresses: [
    "0xVaultV1Address0000000000000000000000000000",
  ] as Address[],
  block: { number: block.number, timestamp: block.timestamp },
});

const targetReallocations = targetMarket.getReallocations({
  reallocationData: targetReallocationData,
  operation: "borrow",
  amount: parseUnits("5", 18),
});

const refinanceWithReallocations = sourceMarket.refinance({
  userAddress,
  positionData: sourcePosition,
  target: {
    marketParams: targetMarketParams,
    positionData: targetPosition,
  },
  collateralAmount: parseUnits("5", 18),
  borrowAssets: parseUnits("5", 18),
  targetReallocations,
});

const refinanceWithReallocationsTx = refinanceWithReallocations.buildTx();
// `refinanceWithReallocationsTx.value` includes PublicAllocator fees from `targetReallocations`.

See the Actions overview on the Morpho SDK page for the full route table and its Errors and invariants for the Blue error classes.