(De)List Markets (in MorphoMarketV1AdapterV2)
Once your MorphoMarketV1AdapterV2 is deployed and enabled, you can control which Morpho Variable Rate markets it may allocate to by setting per-market risk caps.
Listing a market means setting non-zero caps for it and allocating assets to it. Delisting means removing those caps and withdrawing all allocated assets.
Listing a Market
Method 1: via the Curator App V2 (Recommended)
On your Morpho Vault V2 page, open the Caps tab.
- Click "Add collateral" to set the two collateral token caps (absolute and relative), or skip if the collateral is already listed.
- Click "Add market" to set the two market caps (absolute and relative) for the specific market.
For each cap, the process is two steps:
- Submit the cap proposal.
- Once the timelock has elapsed, click "Timelocks" to accept it.
The market will be listed in the MorphoMarketV1AdapterV2 once you have allocated some assets.
Method 2: via Script
The flow has two stages: the Curator submits cap increases; after each proposal becomes executable, anyone can execute the same calldata. Split the stages into separate runs when their timelocks are non-zero. Check executableAt(data) and that the proposal has not been revoked or its selector abdicated.
Use IVaultV2 from vault-v2/interfaces/IVaultV2.sol and MarketParams from vault-v2/adapters/interfaces/IMorphoMarketV1AdapterV2.sol. In the snippets below, vault, adapterAddress, marketParams, and all four proposed cap values are inputs you must define. adapterAddress is the deployed adapter, not the script address.
- Absolute caps are in raw units of the vault asset and must fit in
uint128. - Relative caps are WAD-scaled (
1e18= 100%) and cannot exceed1e18. - Each proposed increase must be at least the current corresponding cap when executed. Omit unchanged caps, and do not submit duplicate pending calldata.
- Collateral caps apply across markets sharing that collateral within the vault. Choose explicit bounds; do not make this shared cap unlimited merely to list one market.
Step 1 — submit as Curator:
bytes memory collateralIdData = abi.encode("collateralToken", marketParams.collateralToken);
bytes memory marketIdData = abi.encode("this/marketParams", adapterAddress, marketParams);
vault.submit(abi.encodeCall(IVaultV2.increaseAbsoluteCap, (collateralIdData, collateralAbsoluteCap)));
vault.submit(abi.encodeCall(IVaultV2.increaseRelativeCap, (collateralIdData, collateralRelativeCap)));
vault.submit(abi.encodeCall(IVaultV2.increaseAbsoluteCap, (marketIdData, marketAbsoluteCap)));
vault.submit(abi.encodeCall(IVaultV2.increaseRelativeCap, (marketIdData, marketRelativeCap)));Step 2 — execute the submitted changes once executable:
bytes memory collateralIdData = abi.encode("collateralToken", marketParams.collateralToken);
bytes memory marketIdData = abi.encode("this/marketParams", adapterAddress, marketParams);
vault.increaseAbsoluteCap(collateralIdData, collateralAbsoluteCap);
vault.increaseRelativeCap(collateralIdData, collateralRelativeCap);
vault.increaseAbsoluteCap(marketIdData, marketAbsoluteCap);
vault.increaseRelativeCap(marketIdData, marketRelativeCap);These calls only configure caps. They do not create the market, allocate assets, or change the liquidity adapter. The adapter-wide caps must also permit the intended allocation; see Allocating to a Listed Market for the next step.
Method 3: via Etherscan
1. Prepare the cap ID data
The idData bytes are passed directly to the cap functions (the vault hashes them internally):
bytes memory collateralIdData = abi.encode("collateralToken", collateralTokenAddress);
bytes memory marketIdData = abi.encode("this/marketParams", adapterAddress, marketParams);2. Submit collateral token caps
As Curator, call submit(bytes) on the vault with:
abi.encodeCall(IVaultV2.increaseAbsoluteCap, (collateralIdData, capAmount))abi.encodeCall(IVaultV2.increaseRelativeCap, (collateralIdData, relativeCapWad))(where1e18= 100%)
3. Submit market caps
Call submit(bytes) again with:
abi.encodeCall(IVaultV2.increaseAbsoluteCap, (marketIdData, capAmount))abi.encodeCall(IVaultV2.increaseRelativeCap, (marketIdData, relativeCapWad))(where1e18= 100%)
4. Accept after timelock
Once the timelock has elapsed, call the functions directly:
increaseAbsoluteCap(collateralIdData, capAmount)increaseRelativeCap(collateralIdData, relativeCapWad)(where1e18= 100%)increaseAbsoluteCap(marketIdData, capAmount)increaseRelativeCap(marketIdData, relativeCapWad)(where1e18= 100%)
Delisting a Market
A market is considered fully delisted when its caps are 0 and its allocation is 0. Both steps are independent: setting caps to zero is safe even while assets are still allocated (it just prevents new allocations), and you can deallocate at any time while caps are non-zero. The typical order is to cap first, then deallocate when liquidity allows.
Method 1: via the Curator App V2 (Recommended)
1. Reduce caps to zero
On the Caps tab, click "Edit Caps" and set both the absolute and relative caps for the market (and optionally the collateral token, if no other market uses it) to 0. Cap decreases are instant and do not require a timelock, they can also be done by a Sentinel.
Set both the Absolute Cap and Relative Cap fields to 0 and confirm the transaction.
2. Deallocate assets from the market
On the Allocation tab, move all remaining assets out of the target market.
Method 2: via Script
Before running the example, independently verify the intended existing Morpho Blue market on the selected chain and set its ID as MARKET_ID (not the vault cap ID). Do not derive this expected ID from the same environment variables you are checking. The script compares the entire MarketParams tuple against that ID and confirms the market exists on the adapter's Morpho deployment before any entrypoint broadcasts. Revoke pending cap increases for the targeted IDs. If this market is the vault's configured liquidity destination, have an Allocator change that routing before zeroing its caps; otherwise new deposits routed to it can revert. Pending-proposal revocation and liquidity-route changes are not performed by this script. See Unsafe Market: Soft Deprecation.
This example verifies the adapter through a trusted factory. Set MORPHO_MARKET_V1_ADAPTER_V2_FACTORY independently from the selected chain's MorphoMarketV1AdapterV2Factory in the official contract addresses. Do not derive this trust input from the candidate adapter's factory() getter. The script requires MORPHO_MARKET_V1_ADAPTER_V2 to equal the factory's morphoMarketV1AdapterV2(vault) lookup, rejects an empty lookup, and retains the vault/market checks. Verify the factory address yourself; the script cannot establish that a user-supplied factory is official.
The script has three entrypoints:
run()— Curator or Sentinel: zeros the market's absolute and relative caps. Shared collateral caps are left unchanged by default. SetCLEAR_COLLATERAL_CAPS=trueonly if intentionally disabling that collateral for every market/adapter in the vault that shares its collateral ID.withdrawAssets(uint256)— Allocator or Sentinel: deallocates an explicit amount of the vault's underlying asset, in raw units, after both market caps are zero. An account with only the Curator role cannot perform this step.syncAllocation()— Allocator or Sentinel: handles a zero-recovery position with zero market caps, zero expected assets, and a positive stored allocation. It synchronizes accounting without withdrawing tokens or burning shares; it does not recover lost assets.
Do not use vault.allocation(id) as a live withdraw-all quote: it is stored accounting and can lag interest or losses. Inspect adapter.expectedSupplyAssets(marketId) for the adapter's current expected holding and check the market's available liquidity. Choose an amount within both bounds; partial withdrawals may be needed. Values can change before inclusion, so simulate again and retry if necessary.
Local setup (Foundry required). Start in a new empty directory. Initialize the Foundry project before installing dependencies:
forge init --empty --no-git --no-deps .
mkdir -p lib
git clone https://github.com/morpho-org/vault-v2.git lib/vault-v2
git -C lib/vault-v2 checkout --detach a0ba9df0ea697a080c0de69c18b84738cfb3bef7
git -C lib/vault-v2 submodule update --init --recursiveThe initialization creates foundry.toml, src/ and script/. --no-deps avoids a separate top-level forge-std installation; the mappings below use Vault V2's pinned copy. Do not run initialization after cloning dependencies into the directory. If using an existing Foundry project, skip initialization and integrate the configuration below instead of overwriting your existing settings.
If lib/vault-v2 already exists, do not clone over it: verify that it is on the same revision before initializing its submodules. The pin keeps this example's dependency versions reproducible.
Merge these settings into foundry.toml; do not duplicate an existing [profile.default] section:
[profile.default]
solc_version = "0.8.28"
evm_version = "cancun"Create remappings.txt in the project root with these entries (or update an existing file, replacing conflicting mappings for the same prefixes):
vault-v2/=lib/vault-v2/src/
morpho-blue/=lib/vault-v2/lib/morpho-blue/
forge-std/=lib/vault-v2/lib/forge-std/src/Keep the second mapping inside lib/vault-v2/, not a separate lib/morpho-blue/ installation. Vault V2's own relative imports must resolve to the same Morpho Blue source files as the snippet's morpho-blue/ imports. A separate copy defines a different Solidity MarketParams type and can cause type-conversion errors in typed adapter or Morpho calls, even if its source contents match. The forge-std/ mapping likewise uses Vault V2's installed dependency.
script/DelistMarket.s.sol
pragma solidity 0.8.28;
import {Script, console} from "forge-std/Script.sol";
import {IVaultV2} from "vault-v2/interfaces/IVaultV2.sol";
import {IMorphoMarketV1AdapterV2, MarketParams} from "vault-v2/adapters/interfaces/IMorphoMarketV1AdapterV2.sol";
import {IMorphoMarketV1AdapterV2Factory} from "vault-v2/adapters/interfaces/IMorphoMarketV1AdapterV2Factory.sol";
import {IMorpho, Id} from "morpho-blue/src/interfaces/IMorpho.sol";
contract DelistMarket is Script {
function run() external {
(IVaultV2 vault, address adapterAddress, MarketParams memory marketParams) = _config();
bytes memory marketIdData = abi.encode("this/marketParams", adapterAddress, marketParams);
bool clearCollateralCaps = vm.envOr("CLEAR_COLLATERAL_CAPS", false);
vm.startBroadcast();
vault.decreaseAbsoluteCap(marketIdData, 0);
vault.decreaseRelativeCap(marketIdData, 0);
if (clearCollateralCaps) {
bytes memory collateralIdData = abi.encode("collateralToken", marketParams.collateralToken);
vault.decreaseAbsoluteCap(collateralIdData, 0);
vault.decreaseRelativeCap(collateralIdData, 0);
}
vm.stopBroadcast();
console.log("Cap-reduction calls prepared; verify their onchain confirmation before withdrawing.");
}
function withdrawAssets(uint256 assets) external {
(IVaultV2 vault, address adapterAddress, MarketParams memory marketParams) = _config();
bytes32 id = keccak256(abi.encode("this/marketParams", adapterAddress, marketParams));
require(vault.absoluteCap(id) == 0 && vault.relativeCap(id) == 0, "Zero market caps first");
require(assets > 0, "Choose a positive asset amount");
vm.startBroadcast();
vault.deallocate(adapterAddress, abi.encode(marketParams), assets);
vm.stopBroadcast();
console.log("Requested deallocation in raw asset units:", assets);
}
function syncAllocation() external {
(IVaultV2 vault, address adapterAddress, MarketParams memory marketParams) = _config();
bytes32 id = keccak256(abi.encode("this/marketParams", adapterAddress, marketParams));
bytes32 marketId = keccak256(abi.encode(marketParams));
require(vault.absoluteCap(id) == 0 && vault.relativeCap(id) == 0, "Zero market caps first");
require(vault.allocation(id) > 0, "No allocation to sync");
require(IMorphoMarketV1AdapterV2(adapterAddress).expectedSupplyAssets(marketId) == 0, "Assets remain");
vm.startBroadcast();
vault.deallocate(adapterAddress, abi.encode(marketParams), 0);
vm.stopBroadcast();
console.log("Accounting-sync call prepared; verify the allocation after confirmation.");
}
function _config() internal view returns (IVaultV2 vault, address adapterAddress, MarketParams memory marketParams) {
vault = IVaultV2(vm.envAddress("VAULT_V2_ADDRESS"));
adapterAddress = vm.envAddress("MORPHO_MARKET_V1_ADAPTER_V2");
{
IMorphoMarketV1AdapterV2Factory factory = IMorphoMarketV1AdapterV2Factory(
vm.envAddress("MORPHO_MARKET_V1_ADAPTER_V2_FACTORY")
);
address expectedAdapter = factory.morphoMarketV1AdapterV2(address(vault));
require(expectedAdapter != address(0) && adapterAddress == expectedAdapter, "Wrong factory adapter");
}
marketParams = MarketParams({
loanToken: vm.envAddress("LOAN_TOKEN"),
collateralToken: vm.envAddress("COLLATERAL_TOKEN"),
oracle: vm.envAddress("ORACLE"),
irm: vm.envAddress("IRM"),
lltv: vm.envUint("LLTV")
});
bytes32 expectedMarketId = vm.envBytes32("MARKET_ID");
require(keccak256(abi.encode(marketParams)) == expectedMarketId, "Market ID mismatch");
IMorphoMarketV1AdapterV2 adapter = IMorphoMarketV1AdapterV2(adapterAddress);
require(adapter.parentVault() == address(vault) && vault.isAdapter(adapterAddress), "Wrong or disabled adapter");
require(marketParams.loanToken == vault.asset() && marketParams.irm == adapter.adaptiveCurveIrm(), "Market mismatch");
require(IMorpho(adapter.morpho()).market(Id.wrap(expectedMarketId)).lastUpdate != 0, "Market does not exist");
}
}Save the example as script/DelistMarket.s.sol and check compilation before proceeding:
forge buildEnvironment setup (Bash). Create a local .env file in the project root. Fill the contract, market and RPC values before any run. Set CAP_ACCOUNT for cap reduction and WITHDRAW_ACCOUNT for either follow-up operation; set ASSETS_TO_WITHDRAW only for a positive-amount withdrawal. Keep this file out of version control and do not put private keys in it.
# Vault and enabled adapter on the selected chain.
VAULT_V2_ADDRESS=""
MORPHO_MARKET_V1_ADAPTER_V2=""
# Independently verified factory from the official addresses for this chain.
MORPHO_MARKET_V1_ADAPTER_V2_FACTORY=""
# Exact parameters of the independently verified existing market.
LOAN_TOKEN=""
COLLATERAL_TOKEN=""
ORACLE=""
IRM=""
LLTV=""
# Morpho Blue market ID, not the vault's cap ID.
MARKET_ID=""
# RPC endpoint and names of local encrypted Foundry keystore accounts.
RPC_URL=""
CAP_ACCOUNT=""
WITHDRAW_ACCOUNT=""
# Positive integer in raw loan-token units; unused by syncAllocation().
ASSETS_TO_WITHDRAW=""
# Leave false unless deliberately disabling the shared collateral everywhere.
CLEAR_COLLATERAL_CAPS=false- Copy the full market tuple from the intended market on the same chain.
LLTVmust be its exact raw integer, not a human-readable percentage. ObtainMARKET_IDindependently of these inputs, as described above. CAP_ACCOUNTis the local keystore name for cap reduction; thedecreaseAbsoluteCapreference lists its callers as "Curator, Sentinel".WITHDRAW_ACCOUNTis the keystore name for withdrawal or sync; thedeallocatereference lists "Allocator, Sentinel". These are names for--account, not wallet addresses or private keys. A Sentinel can use the same account for both stages.ASSETS_TO_WITHDRAWis an integer amount of the vault's underlying asset in raw units, not shares or USD. Choose it from the current holdings and available liquidity, not the stored allocation. It is not used for the zero-asset sync.
Load the values into the same Bash shell that will run the commands below:
set -a
source .env || exit 1
set +aOnly source a file you created and reviewed: Bash executes its contents. Foundry loading .env internally does not populate the parent shell before it expands the command arguments. After editing .env, load it again.
With these values loaded, simulate each command without --broadcast first. After a successful simulation, repeat that command with --broadcast; confirm Stage 1 onchain before simulating either Stage 2 operation.
# Stage 1: reduce caps, then wait for confirmation.
forge script script/DelistMarket.s.sol \
--rpc-url "${RPC_URL:?Set RPC_URL in .env}" --account "${CAP_ACCOUNT:?Set CAP_ACCOUNT in .env}" --broadcast
# Stage 2: choose ASSETS_TO_WITHDRAW in raw asset units based on current holdings/liquidity.
forge script script/DelistMarket.s.sol \
--sig 'withdrawAssets(uint256)' "${ASSETS_TO_WITHDRAW:?Set a positive raw asset amount in .env}" \
--rpc-url "${RPC_URL:?Set RPC_URL in .env}" --account "${WITHDRAW_ACCOUNT:?Set WITHDRAW_ACCOUNT in .env}" --broadcastZero-asset accounting sync. If a loss leaves expectedSupplyAssets(marketId) == 0 while vault.allocation(id) > 0, use this alternative to the positive-amount withdrawal in Stage 2, after confirming both market caps are zero:
forge script script/DelistMarket.s.sol \
--sig 'syncAllocation()' \
--rpc-url "${RPC_URL:?Set RPC_URL in .env}" --account "${WITHDRAW_ACCOUNT:?Set WITHDRAW_ACCOUNT in .env}" --broadcastSimulate without --broadcast first. This calls deallocate(adapterAddress, abi.encode(marketParams), 0) to update the stored allocation and active-market list. It does not recover losses, withdraw tokens, or burn the remaining shares. It is not a workaround for illiquidity when expected assets are still positive. After confirmation, re-read the allocation and expected assets; do not repeat once the stored allocation is zero. The full market-ID checks apply to this entrypoint too.
The console output is not proof of full delisting. After confirmation, check both market caps and the adapter's expectedSupplyAssets(marketId) / supplyShares(marketId), where marketId = keccak256(abi.encode(marketParams)). Material residual assets require further deallocation. Rounding dust must be distinguished from interest or other remaining exposure; zero caps alone do not prove the position was fully withdrawn.
Method 3: via Etherscan
1. Prepare the cap ID data
bytes memory marketIdData = abi.encode("this/marketParams", adapterAddress, marketParams);
bytes memory collateralIdData = abi.encode("collateralToken", collateralTokenAddress);2. Decrease market caps to zero
As Curator or Sentinel, call directly (no timelock required for decreases):
decreaseAbsoluteCap(marketIdData, 0)decreaseRelativeCap(marketIdData, 0)
3. (Optional) Decrease collateral token caps to zero
Only if no other listed market uses the same collateral asset:
decreaseAbsoluteCap(collateralIdData, 0)decreaseRelativeCap(collateralIdData, 0)
4. Deallocate remaining assets
As Allocator, call deallocate(adapterAddress, marketData, amount) where marketData = abi.encode(marketParams).
Allocating to a Listed Market
Once caps are accepted, the Allocator can direct assets to the market via the Curator App V2.
On your Morpho Vault V2 page, open the Allocation tab and click "Reallocate".
Enter the desired allocation amount for each market in the Allocation column, then click "Save" to confirm.
To set this market as the vault's liquidity source for user deposits and withdrawals, see Liquidity Curation.