Vault V2
Introduction
VaultV2 is Morpho's curated vault: depositors supply the vault's asset and receive shares, while the vault allocates the liquidity across markets through adapters.
The Morpho SDK builds every vault transaction - deposit (with native-token wrapping), withdraw, redeem, and the force-withdraw / force-redeem escape hatches - through the same getRequirements / buildTx flow as every other Morpho surface, via client.morpho.vaultV2(address, chainId).
VaultV1 (MetaMorpho) mirrors this vault surface and adds a V1→V2 migration - see the Actions overview for its route table.
Setup
Build the extended client once, as shown in the Morpho SDK Setup, then construct the vault entity. Every flow below reuses client, the vault entity, and userAddress - the client's connected account, per the Builder = signer invariant. Dispatch requirements with the loop from The getRequirements flow.
import { type Address, parseUnits } from "viem";
import { mainnet } from "viem/chains";
// `client` is the extended wallet client from the Morpho SDK Setup;
// `userAddress` is its connected account.
const userAddress = USER_ADDRESS;
// chainId is mandatory (validated against the viem client).
const vault = client.morpho.vaultV2(
"0xVaultV2Address0000000000000000000000000000" as Address,
mainnet.id,
);Fetch vault state
// Fetches on-chain vault state with accrued interest.
// Returns an `AccrualVaultV2` with: address, asset, totalAssets, totalSupply,
// share/asset conversion helpers (`toShares`, `toAssets`), curated allocations,
// and adapters used by `forceWithdraw` / `forceRedeem`.
const vaultData = await vault.getData();Deposit
// Routed through Bundler3 via GeneralAdapter1 - enforces `maxSharePrice`
// (ERC-4626 inflation-attack guard) and supports atomic native-token wrapping.
const deposit = vault.deposit({
amount: parseUnits("1", 18),
userAddress,
vaultData,
// slippageTolerance is a WAD-scaled bigint: 1e18 = 100%. It defaults to
// DEFAULT_SLIPPAGE_TOLERANCE = 300000000000000n (0.03%) and is capped at 10%.
// e.g. 0.1%: parseUnits("0.001", 18) = 1000000000000000n
// slippageTolerance: parseUnits("0.001", 18),
});
const requirements = await deposit.getRequirements();
// e.g. [erc20Approval tx for GeneralAdapter1] OR [Permit / Permit2 signature requirement]
const depositTx = deposit.buildTx(/* [requirementSignature] */);Routed through Bundler3 via GeneralAdapter1 - the SDK enforces a maxSharePrice derived from accrualVault and slippageTolerance, preventing ERC-4626 inflation-attack share-price drift.
slippageTolerance is a WAD-scaled bigint - 1e18 (parseUnits("1", 18)) means 100%. It defaults to DEFAULT_SLIPPAGE_TOLERANCE = 300000000000000n (3e14, i.e. 0.03%) and is capped at MAX_SLIPPAGE_TOLERANCE = 100000000000000000n (1e17, i.e. 10%) - a larger value throws ExcessiveSlippageToleranceError, a negative one NegativeSlippageToleranceError. So to pass 0.1%, use parseUnits("0.001", 18) (= 1000000000000000n) - not 0.1, 0.001, or 1n. The same convention applies to every slippageTolerance parameter across the SDK.
Deposit with native-token wrapping
// For vaults whose underlying asset is the chain's wNative token (WETH on mainnet),
// you can deposit native ETH that the bundler atomically wraps before depositing.
const nativeDeposit = vault.deposit({
nativeAmount: parseUnits("1", 18), // 1 ETH wrapped → WETH → deposited
userAddress,
vaultData,
});
const nativeDepositTx = nativeDeposit.buildTx();
// Mixed (ERC-20 + native) is also supported in a single bundle:
const mixedDeposit = vault.deposit({
amount: parseUnits("0.5", 18), // already-held WETH
nativeAmount: parseUnits("0.5", 18), // raw ETH wrapped at execution
userAddress,
vaultData,
});
const mixedDepositTx = mixedDeposit.buildTx();The bundler atomically transfers the native token, wraps it to the chain's wNative (e.g. WETH), and deposits alongside any ERC-20 amount. The transaction's value is set to nativeAmount. Throws NativeAmountOnNonWNativeVaultError if the vault asset is not the chain's wNative.
Withdraw
// Direct vault call - no bundler overhead, no Morpho authorization required.
const withdraw = vault.withdraw({
amount: parseUnits("0.5", 18),
userAddress,
});
const withdrawTx = withdraw.buildTx();Redeem
// Direct vault call - same as withdraw but specifies an exact share amount,
// so it is immune to share-price drift between quoting and execution.
const redeem = vault.redeem({
shares: parseUnits("1", 18),
userAddress,
});
const redeemTx = redeem.buildTx();Force withdraw / force redeem
For VaultV2, when the vault's idle liquidity is not sufficient to satisfy a withdrawal, you can pull liquidity back from specific markets / adapters first by encoding a forceDeallocate chain followed by a single withdraw (or redeem) - all inside the vault's native multicall.
// Encodes N `forceDeallocate` calls + 1 `withdraw` in a single VaultV2 multicall.
// Use this when the vault's idle liquidity is insufficient and you need to
// pull liquidity back from specific markets/adapters before withdrawing.
const forceWithdraw = vault.forceWithdraw({
deallocations: [
{
adapter: "0xAdapter0000000000000000000000000000000000",
amount: parseUnits("0.5", 18),
},
],
withdraw: { amount: parseUnits("0.5", 18) },
userAddress, // penalty source AND withdraw recipient
});
const forceWithdrawTx = forceWithdraw.buildTx();// Share-based counterpart to forceWithdraw. The deallocated total must be
// >= the asset-equivalent of the redeemed shares; apply a buffer for share-price drift.
const forceRedeem = vault.forceRedeem({
deallocations: [
{
adapter: "0xAdapter0000000000000000000000000000000000",
amount: parseUnits("1.01", 18), // small buffer over target assets
},
],
redeem: { shares: parseUnits("1", 18) },
userAddress,
});
const forceRedeemTx = forceRedeem.buildTx();See the Actions overview on the Morpho SDK page for the full route table and its Errors and invariants for the vault error classes.