Blue action metadata now exposes args.referralFeeAssets: the exact loan-token referral-fee amount the encoded BlueBundlesV1 call charges. blueSupply reports the fee deducted from assets before supplying (floor(assets * referralFeePct / WAD)); blueSupplyCollateral, blueBorrow and blueSupplyCollateralBorrow report the fee deducted from net borrow proceeds (floor((borrowAssets - reallocationPenaltyAssets) * referralFeePct / WAD)); blueWithdraw reports it only in exact-assets mode (omitted in exact-shares mode, where the withdrawn amount is resolved at execution); blueRepay / blueRepayWithdrawCollateral report it only for an exact-assets repay (repayAssets > 0n, omitted in exact-shares mode or when there is no repay leg). @morpho-org/evm-simulation's toSimulationLimits consumes this metadata.
Midnight take flows now reject, before building, takes that MidnightBundlesV2 would always revert:
takeLend, takeBorrow, takeWithdraw, supplyCollateralTakeBorrow and takeRepayWithdrawCollateral throw MidnightTakeableOffersNotFillableError when every offer is expired, cannot start before the deadline, was made by accountAddress, has a continuousFeeCap below the market fee, has zero units, has a tick outside [0, MAX_TICK], is a buy offer with a non-zero receiverIfMakerIsSeller, or would open debt after maturity with no credit left to sell, and the flow has no withdraw or repay fallback, or every offer is dropped as described below (assets targets on buys, any target on sells), even when a withdraw or repay fallback exists. They throw MidnightTakeTargetUnreachableError when the offers plus that fallback cannot hold the target's units or a buy assets target's minUnits, or a buy units target's maxBuyerAssets cannot pay the cheapest fill plus the referral fee.
takeLend and takeRepayWithdrawCollateral throw MidnightContinuousFeeAboveMaxError when maxContinuousFee is below marketData.continuousFee, and on an assets target drop sell offers whose tick is outside [0, MAX_TICK] or whose buyer price is zero or above one, which used to revert the whole bundle with Panic(0x12) or PriceGreaterThanOne().
takeBorrow, takeWithdraw and supplyCollateralTakeBorrow drop buy offers whose tick is outside [0, MAX_TICK], whose price is zero, or whose price stays below the settlement fee until the deadline (at or below it on an assets target), which used to revert the whole bundle with Panic(0x12) or an arithmetic underflow.
takeRepayWithdrawCollateral and repayWithdrawCollateral throw MidnightRepayExceedsDebtError when the repay target exceeds positionData.debt; a full repayWithdrawCollateral throws MidnightTakeTargetUnreachableError when maxBuyerAssets is below the debt.
The midnightTakeBorrow, midnightTakeWithdraw and midnightSupplyCollateralTakeBorrow builders throw MidnightTakeTargetUnreachableError when minSellerAssets exceeds units minus the referral fee, or maxUnits is below assets plus the referral fee.
Each check uses only the inputs, the market and position snapshots, and the current time; offers that are dead while another offer can fill are still passed through.
Patch Changes
Operation reallocation plans now treat idle legs that together take (nearly) all of a source vault's idle, within allocatorCapHeadroom, as liquidity-bound like a leg filling an allocator cap. A borrow or withdrawal without shortfall whose plan draws only on such idle no longer plans it. When other vaults' legs stay unbound, the reduced plan can still take that idle.
Note: This package is in beta. Please test in a dev setup first.
A WDK lending module for Morpho on EVM chains. It keeps the same package shape as the Aave WDK lending module, but delegates Morpho vault and market transaction construction to @morpho-org/morpho-sdk.
About WDK
This module follows Wallet Development Kit lending protocol conventions and accepts WDK-compatible EVM wallet accounts.
Features
Deposit into Morpho Vault V2 earn targets.
Withdraw from Morpho Vaults V2.
Supply and withdraw collateral in a Morpho Blue market through BlueBundlesV1.
Borrow and repay from a configured Morpho Blue market through BlueBundlesV1.
Opt into Vault V2 BluePublicAllocator reallocations for borrow liquidity.
pnpm installpnpm --filter @morpho-org/wdk-protocol-lending-morpho-evm test
Quick Start
import MorphoProtocolEvm from '@morpho-org/wdk-protocol-lending-morpho-evm'import { createPublicClient, createWalletClient, custom, http } from 'viem'import { mainnet } from 'viem/chains'// Use any WDK-compatible EVM wallet account and its EIP-1193 provider.const userAddress = await account.getAddress()const publicClient = createPublicClient({ chain: mainnet, transport: http() })const walletClient = createWalletClient({ account: userAddress, chain: mainnet, transport: custom(provider)})const morpho = new MorphoProtocolEvm(account, { presets: { earn: 'sky-money-usdt-savings', borrow: 'wsteth' }, supportSignature: true})// Prepare the deposit once, then reuse the same handle for requirements and submission.const prepared = await morpho.prepareSupply({ token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', amount: 1000000n})// Send approvals and retain any permit/Permit2 signature for this prepared handle.// Keep the explicit Permit2 nonce unique and unused for this owner.let requirementSignaturefor (const requirement of await prepared.getRequirements({ permit2Nonce: 42n })) { if ('sign' in requirement) { requirementSignature = await requirement.sign(walletClient, userAddress) } else { const { hash } = await account.sendTransaction({ to: requirement.to, value: requirement.value, data: requirement.data }) await publicClient.waitForTransactionReceipt({ hash }) }}// Submit through the same handle so its captured requirement matches the signature.await prepared.submit(requirementSignature)
Configuration
new MorphoProtocolEvm(account, options)
Options:
chainId (number | bigint): required when using explicit Morpho targets; validates reads, requirements, quotes, and sends against provider/target chain mismatches and switches.
Built-in presets already carry their expected chain id. If you use earnVaultAddress, borrowMarketParams, or borrowMarketId directly, pass chainId so the adapter checks the provider at the start and terminal boundary of each operation. EOA transactions are signed for that chain and verified before broadcast; ERC-4337 preparation, signing, and broadcast execute atomically through WDK with the signing chain bound to the validated context.
ERC-4337 accounts cache chain-bound UserOperation state. If an account has already cached another chain, create a fresh wallet account and MorphoProtocolEvm adapter before continuing.
Prepared vault deposits accept MorphoExclusiveSupplyOptions and Blue collateral methods accept MorphoCollateralSupplyOptions; both types require exactly one of amount or nativeAmount. nativeAmount is only valid when the configured vault asset or collateral token is the wrapped native token for the chain.
Methods
Method
Description
supply(options, config?)
Deposit through VaultBundlesV1 using existing approvals or native funding
quoteSupply(options, config?)
Quote a VaultBundlesV1 deposit using existing approvals or native funding
prepareSupply(options)
Prepare one vault deposit handle exposing getRequirements, submit, and quote
withdraw(options, config?)
Withdraw assets from the configured vault; throws UnresolvedVaultWithdrawRequirementsError unless the exact VaultBundlesV1 share allowance is already in place
prepareWithdraw(options)
Prepare one vault withdrawal handle exposing getRequirements, submit, and quote
quoteWithdraw(options, config?)
Quote vault withdrawal when its exact share allowance is already in place
supplyCollateral(options, config?)
Supply collateral to the configured market
getSupplyCollateralRequirements(options)
Return SDK requirements for collateral supply
quoteSupplyCollateral(options, config?)
Quote collateral supply
borrow(options, config?)
Borrow from the configured market
getBorrowRequirements(options)
Return BlueBundlesV1 authorization requirements
quoteBorrow(options, config?)
Quote borrow
repay(options, config?)
Repay by assets, or pass amount: 'max' to repay current borrow shares
getRepayRequirements(options)
Return SDK requirements for repay
quoteRepay(options, config?)
Quote repay
withdrawCollateral(options, config?)
Withdraw collateral from the configured market
getWithdrawCollateralRequirements(options)
Return BlueBundlesV1 authorization requirements for collateral withdrawal
quoteWithdrawCollateral(options, config?)
Quote collateral withdrawal
getVaultPosition(account?)
Read configured vault position
getMarketPosition(account?)
Read configured market position
getAccountData(account?)
Read combined configured vault and market position
Presets
Earn presets target Ethereum mainnet USDT vaults:
Preset
Vault
sky-money-usdt-savings
sky.money USDT Savings V2
steakhouse-prime-instant
Steakhouse Prime Instant V2
This module only builds Morpho Vault V2 earn flows. pnpm run check:vault-v2 verifies that code, docs, and tests do not reintroduce Morpho Vault V1 usage or an earn-flow selector.
Use prepareSupply to deposit through VaultBundlesV1. Supply exactly one of amount or
nativeAmount, resolve requirements on the returned handle, then call its submit or quote.
The standard WDK supply and quoteSupply methods use the same VaultBundlesV1 route for
approved ERC-20 assets or native funding; signed deposits use the prepared handle.
ERC-20 approvals and ERC-2612 permits target VaultBundlesV1. Permit2 uses SignatureTransfer,
with an ERC-20 approval to Permit2 and a signed transfer naming VaultBundlesV1 as spender.
See MIGRATION.md for migration from the removed legacy supply route.
Morpho SDK Requirements
Morpho SDK actions can require approvals, permit/permit2 signatures, or Morpho authorization before the final action. This module exposes those requirements through get*Requirements methods — and, for vault deposits, through the prepareSupply handle's own getRequirements — rather than reimplementing allowance or authorization logic.
When prepareSupply(...).getRequirements, getRepayRequirements, or getSupplyCollateralRequirements selects Permit2 SignatureTransfer, the Morpho SDK resolves the lowest unused permit2Nonce for the account by default. Pass an explicit unused permit2Nonce in the optional RequirementOptions argument only when concurrent flows for the same owner need to partition nonces themselves; the adapter forwards it unchanged.
For ERC-4337 accounts you can choose to batch the returned requirement transactions with the final transaction using your account-level flow. For EOA accounts, send requirements before the final operation.
Requirement entries are one of:
Approval transaction: send the returned transaction before the final action.
Morpho authorization transaction: send the returned setAuthorization transaction before a borrow or collateral withdrawal that requires BlueBundlesV1 authorization.
Signature request: call the returned requirement's sign(client, userAddress) method, then pass the resulting requirementSignature to the corresponding repay, supplyCollateral, borrow, or withdrawCollateral call. Prepared vault deposits take theirs on the handle's submit(requirementSignature) or quote(requirementSignature).
Vault-share approval or permit: vault withdrawals route through VaultBundlesV1, which burns the account's vault shares, so prepareWithdraw(options).getRequirements() returns the exact share approval — or a signable ERC-2612 shares permit when supportSignature is enabled — that must be satisfied before submit().
BlueBundlesV1 calls use a two-hour deadline; signed calls reuse the requirement signature's deadline as long as it is within two hours (plus a 300-second clock-skew allowance) of submission — a longer deadline throws BlueBundlesV1DeadlineExceedsWindowError. Unsigned calls default to a two-hour deadline.
For withdrawal quotes, keep the same prepared handle: confirm its approval before prepared.quote(),
or pass its signed share permit to prepared.quote(signedPermit). Both quoteWithdraw() and unsigned
prepared.quote() throw UnresolvedVaultWithdrawRequirementsError when the exact share allowance
is missing, including when an existing allowance exceeds the cap. Quoting does not execute approvals
or consume permits. Every prepared-withdrawal method rechecks the provider chain and throws
ChainIdMismatchError if the wallet has switched away from the configured vault chain.
Morpho SDK enforces a builder/executor invariant for bundled actions. For that reason, onBehalfOf and vault/collateral withdrawal to must equal the connected wallet address in this WDK adapter.
Borrow reallocations use Vault V2 BluePublicAllocator exclusively. Vault V1
reallocation types are no longer accepted:
See Migrating to 2.0 for the complete breaking-change checklist.
Fork E2E Test
The colocated unit suite runs as part of the workspace's pnpm test command. The Anvil-fork integration suite under test/ uses *.integration.test.ts names and is gated on ERPC_SECRET being set; the corresponding tests are skipped otherwise. To execute the real vault deposit path against a mainnet fork:
ERPC_SECRET=<key> pnpm test --project wdk-protocol-lending-morpho-evm-fork
The fork test impersonates a USDT holder, funds the local test wallet, sends SDK requirements, and executes a Morpho Vault V2 deposit against forked mainnet state.