Depositing & Withdrawing from Vaults
Morpho Vault V2 follows the standard ERC‑4626 interface for deposit and withdrawal operations. That means deposit(), withdraw(), mint(), and redeem() behave like familiar tokenized vault flows for new Earn integrations on Morpho.
Morpho Vault V2 has a non-conventional behavior on max functions (maxDeposit, maxMint, maxWithdraw, maxRedeem): they always return zero. See the Morpho Vaults V2 reference for details.
For background on ERC4626 mechanics and vault architecture, see Vaults & ERC4626 Mechanics.
This guide shows the recommended integration path for Morpho Vault deposits and withdrawals: the Morpho SDK (@morpho-org/morpho-sdk). It builds final, ready-to-send viem transactions and resolves all on-chain pre-requisites for you - ERC-20 approvals, Permit / Permit2 signatures, native-token wrapping, slippage protection, and Bundler3 routing - whether you're building a dApp frontend or a backend service.
Key Concepts: Assets vs. Shares
When interacting with ERC4626 vaults, you have two approaches for deposits and withdrawals: an asset-first approach or a shares-first approach. Understanding the difference is key to a robust integration.
| Approach | Deposit Function | Withdrawal Function | Description |
|---|---|---|---|
| Asset-First | deposit(assets, ...) | withdraw(assets, ...) | You specify the exact amount of the underlying token (e.g., USDC, WETH) you want to deposit or withdraw. |
| Shares-First | mint(shares, ...) | redeem(shares, ...) | You specify the exact number of vault shares you want to mint or redeem. |
Best Practice:
- For deposits,
deposit()is the most common and intuitive function. - For full withdrawals,
redeem()is recommended. Redeeming all of a user's shares ensures their balance goes to zero and avoids leaving behind small, unusable amounts of "dust." - For partial withdrawals where a user needs a specific amount of the underlying asset,
withdraw()is appropriate.
Prerequisites
Before you begin, you will need:
- The address of the Morpho Vault you want to interact with. You can find active vaults using the Morpho API.
- An account with a balance of the vault's underlying asset (e.g., WETH for a WETH vault).
Vault Safety: Inflation Attack Protection
Before integrating with any ERC4626 vault, verify that adequate inflation attack protection is in place.
All ERC4626-compliant vaults, including Morpho Vaults, require a dead deposit to protect against share inflation attacks. This protection must be verified before your first interaction with a vault.
Verification Check:
// Check that the vault has adequate protection
const deadAddress = "0x000000000000000000000000000000000000dEaD";
const deadShares = await vault.balanceOf(deadAddress);
if (deadShares < 1_000_000_000n) {
throw new Error("Vault lacks required inflation protection");
}Key Points:
- The dead deposit must be at least 1e9 shares for assets with more than 9 decimals, or 1e12 shares otherwise (approximately $1 equivalent).
- This check should be performed during vault integration/whitelisting, not on every transaction
- Properly protected vaults make inflation attacks economically unfeasible
- Morpho Vault V2 integrations should ensure this protection is established by curators
For a detailed explanation of how this protection works, see Vault Mechanics: Inflation Attack Protection.
Integrate with the Morpho SDK
For an end-to-end walkthrough of every vault action this SDK exposes (deposit, withdraw, redeem, force-withdraw) see the Vault subpage; the Morpho SDK page covers the getRequirements flow, the builder = signer invariant, and error classes.
Step 1: Install and set up the client
npm install @morpho-org/morpho-sdk@5.4.1 viem@^2.0.0
# or
pnpm add @morpho-org/morpho-sdk@5.4.1 viem@^2.0.0
# or
yarn add @morpho-org/morpho-sdk@5.4.1 viem@^2.0.0import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
import { morphoViemExtension } from "@morpho-org/morpho-sdk";
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const client = createWalletClient({
account,
chain: mainnet,
transport: http(process.env.RPC_URL_MAINNET),
}).extend(
morphoViemExtension({
// Enables Permit / Permit2 in getRequirements() so users skip the extra approve tx.
supportSignature: true,
}),
);
const vault = client.morpho.vaultV2("0xVaultAddress...", mainnet.id);Step 2: Deposit
import { parseUnits } from "viem";
const vaultData = await vault.getData(); // fresh on-chain state with accrued interest
const deposit = vault.deposit({
amount: parseUnits("1.0", 18), // 1 underlying token
userAddress: account.address,
vaultData,
});
// 1. Resolve approvals / permits before sending the deposit
const requirements = await deposit.getRequirements();
const signatures = [];
for (const req of requirements) {
if ("sign" in req) {
// Permit / Permit2: capture the off-chain signature
signatures.push(await req.sign(client, account.address));
} else {
// ERC-20 approval: send the approval transaction
await client.sendTransaction(req);
}
}
// 2. Build and send the deposit tx (same client that built it - "builder = signer").
// buildTx takes the collected requirement signatures as an array.
const depositTx = deposit.buildTx(signatures);
const depositTxHash = await client.sendTransaction(depositTx);
console.log("Deposit successful:", depositTxHash);Step 3: Withdraw / redeem
// Redeem all of a user's shares (recommended for full exits - leaves no dust)
const userShares = vaultData.toShares(parseUnits("1.0", 18));
const redeem = vault.redeem({
shares: userShares,
userAddress: account.address,
});
const redeemTxHash = await client.sendTransaction(redeem.buildTx());
console.log("Withdrawal successful:", redeemTxHash);For partial withdrawals by exact asset amount, use vault.withdraw({ amount, userAddress }) instead of redeem.
Additional Considerations
Slippage for User Experience
Vault deposits convert assets into shares. Between quote and execution, the vault share price can move because interest accrues or vault state changes. Slippage tolerance lets the transaction revert if the user would receive materially fewer shares than expected.
The SDK computes the maxSharePrice guard from fresh vault data and your slippageTolerance, then routes the deposit through Bundler3 / GeneralAdapter1.
The SDK default is 0.03%; the maximum accepted value is 10%. A tighter value gives stronger price protection but can cause more reverts when vault state changes before inclusion.
Step 1: Set the deposit inputs
Choose the asset amount and maximum tolerated movement before building the SDK transaction. Slippage is expressed in WAD units: parseUnits("0.01", 18) means 1%.
import { parseUnits, type Address } from "viem";
import { mainnet } from "viem/chains";
import {
isRequirementSignature,
type RequirementSignature,
} from "@morpho-org/morpho-sdk";
// Assumes `client` is a viem wallet client extended with `morphoViemExtension()`
// and `account` is the connected signer.
const vaultAddress = "0xVaultAddress..." as Address;
const userAddress = account.address;
const amount = parseUnits("1.0", 18); // replace 18 with the underlying asset decimals
const slippageTolerance = parseUnits("0.01", 18); // 1% in WAD units
const vault = client.morpho.vaultV2(vaultAddress, mainnet.id);
const vaultData = await vault.getData(); // fresh on-chain state with accrued interestStep 2: Build the protected deposit transaction
Pass vaultData and slippageTolerance to the deposit builder so the SDK can build the onchain share-price guard.
const deposit = vault.deposit({
amount,
userAddress,
vaultData,
slippageTolerance,
});Step 3: Resolve approvals and permits
getRequirements() returns approval transactions plus an optional Permit / Permit2 signature. Resolve them before sending the final deposit.
const requirements = await deposit.getRequirements();
const requirementSignatures: RequirementSignature[] = [];
for (const requirement of requirements) {
if (isRequirementSignature(requirement)) {
requirementSignatures.push(await requirement.sign(client, userAddress));
} else {
await client.sendTransaction(requirement);
}
}Step 4: Send the protected deposit
Build the final transaction with the optional signature, then send it with the same client that built the transaction.
const depositTxHash = await client.sendTransaction(deposit.buildTx(requirementSignatures));
console.log("Deposit sent:", depositTxHash);See also Vault Mechanics: Slippage Considerations for more context.