Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c1b6f00
feat(vault-v2): add factory check in fetch utility
Foulks-Plb Jan 21, 2026
43af991
refactor(errors): streamline error class constructors and add Unknown…
Foulks-Plb Jan 22, 2026
3a8c02c
feat(vault-v2): implement factory checks in adapter queries and add U…
Foulks-Plb Jan 22, 2026
c426f64
fix(vault-v2): add factory deployment checks in Morpho adapter queries
Foulks-Plb Jan 22, 2026
05e102c
refactor(vault-v2): consolidate error handling
Foulks-Plb Jan 22, 2026
f6085a0
feat(vault-v2): check vaultV1
Foulks-Plb Jan 22, 2026
f704df7
fix(vault-v2): improve error handling for MetaMorpho factory checks
Foulks-Plb Jan 22, 2026
0d64d6e
refactor(vault-v2): clean up code formatting and improve readability …
Foulks-Plb Jan 22, 2026
dab1db8
refactor(vault-v2): enhance fetchVault function with improved error h…
Foulks-Plb Jan 22, 2026
48a7881
refactor(vault-v2): rename UnknownToFactory and revert if deployless …
Foulks-Plb Jan 30, 2026
22e1606
refactor(errors): streamline error class constructors and improve for…
Foulks-Plb Jan 30, 2026
6e58392
Merge branch 'main' into feature/consu-4354-validate-that-vaults-are-…
Foulks-Plb Jan 30, 2026
3482ce4
refactor(vault-v2): introduce isUnknownOfFactoryError utility for imp…
Foulks-Plb Feb 2, 2026
f08d40e
refactor(vault-v2): update isMetaMorpho handling and put it in promise
Foulks-Plb Feb 2, 2026
c4fba26
Merge branch 'main' into feature/consu-4354-validate-that-vaults-are-…
Foulks-Plb Feb 2, 2026
7ec7c74
Merge branch 'main' into feature/consu-4354-validate-that-vaults-are-…
Foulks-Plb Feb 6, 2026
6411b67
fix(vault-v2): remove duplicate import of MarketParams in VaultV2.ts
Foulks-Plb Feb 6, 2026
d72bbeb
fix(vault-v2): update contract code in GetVaultV2.ts for improved fun…
Foulks-Plb Feb 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/blue-sdk-viem/contracts/GetVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {IMorpho, Id, MarketParams} from "./interfaces/IMorpho.sol";
import {Eip5267Domain} from "./interfaces/IERC20Permit.sol";
import {IMetaMorpho, PendingUint192, PendingAddress} from "./interfaces/IMetaMorpho.sol";
import {IPublicAllocator} from "./interfaces/IPublicAllocator.sol";
import {IMetaMorphoFactory} from "./interfaces/IMetaMorphoFactory.sol";

struct VaultConfig {
address asset;
Expand Down Expand Up @@ -41,12 +42,24 @@ struct VaultResponse {
PublicAllocatorConfig publicAllocatorConfig;
}

error UnknownOfFactory(address factory, address vault);

contract GetVault {
function query(IMetaMorpho vault, IPublicAllocator publicAllocator)
function query(IMetaMorpho vault, IPublicAllocator publicAllocator, IMetaMorphoFactory metaMorphoFactory)
external
view
returns (VaultResponse memory res)
{
if (!metaMorphoFactory.isMetaMorpho(address(vault))) {
// MetaMorpho factory V1.0 only exists on Ethereum (1) and Base (8453)
bool isV1_0Factory = (block.chainid == 1 || block.chainid == 8453)
&& IMetaMorphoFactory(0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101).isMetaMorpho(address(vault));

if (!isV1_0Factory) {
revert UnknownOfFactory(address(metaMorphoFactory), address(vault));
}
}

res.config = VaultConfig({
asset: vault.asset(),
symbol: vault.symbol(),
Expand Down
32 changes: 32 additions & 0 deletions packages/blue-sdk-viem/contracts/interfaces/IMetaMorphoFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import {IMetaMorpho} from "./IMetaMorpho.sol";

/// @title IMetaMorphoFactory
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice Interface of MetaMorpho's factory.
interface IMetaMorphoFactory {
/// @notice The address of the Morpho contract.
function MORPHO() external view returns (address);

/// @notice Whether a MetaMorpho vault was created with the factory.
function isMetaMorpho(address target) external view returns (bool);

/// @notice Creates a new MetaMorpho vault.
/// @param initialOwner The owner of the vault.
/// @param initialTimelock The initial timelock of the vault.
/// @param asset The address of the underlying asset.
/// @param name The name of the vault.
/// @param symbol The symbol of the vault.
/// @param salt The salt to use for the MetaMorpho vault's CREATE2 address.
function createMetaMorpho(
address initialOwner,
uint256 initialTimelock,
address asset,
string memory name,
string memory symbol,
bytes32 salt
) external returns (IMetaMorpho metaMorpho);
}
8 changes: 8 additions & 0 deletions packages/blue-sdk-viem/contracts/vault-v2/GetVaultV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ pragma solidity ^0.8.0;
import {IVaultV2, Caps} from "./interfaces/IVaultV2.sol";
import {IMorphoVaultV1AdapterFactory} from "./interfaces/IMorphoVaultV1AdapterFactory.sol";
import {IMorphoMarketV1AdapterV2Factory} from "./interfaces/IMorphoMarketV1AdapterV2Factory.sol";
import {IVaultV2Factory} from "./interfaces/IVaultV2Factory.sol";
error UnknownOfFactory(address factory, address vault);
import {IMorphoMarketV1AdapterV2} from "./interfaces/IMorphoMarketV1AdapterV2.sol";
import {MarketParams} from "../interfaces/IMorpho.sol";


struct Token {
address asset;
string symbol;
Expand Down Expand Up @@ -44,9 +47,14 @@ struct VaultV2Response {
contract GetVaultV2 {
function query(
IVaultV2 vault,
IVaultV2Factory vaultV2Factory,
IMorphoVaultV1AdapterFactory morphoVaultV1AdapterFactory,
IMorphoMarketV1AdapterV2Factory morphoMarketV1AdapterV2Factory
) external view returns (VaultV2Response memory res) {
if (!vaultV2Factory.isVaultV2(address(vault))) {
revert UnknownOfFactory(address(vaultV2Factory), address(vault));
}

res.token =
Token({asset: vault.asset(), symbol: vault.symbol(), name: vault.name(), decimals: vault.decimals()});
res.asset = vault.asset();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@ pragma solidity ^0.8.0;

import {IMorphoMarketV1Adapter} from "./interfaces/IMorphoMarketV1Adapter.sol";
import {MarketParams} from "../interfaces/IMorpho.sol";
import {IMorphoMarketV1AdapterFactory} from "./interfaces/IMorphoMarketV1AdapterFactory.sol";

struct VaultV2MorphoMarketV1AdapterResponse {
address parentVault;
address skimRecipient;
MarketParams[] marketParamsList;
}

error UnknownOfFactory(address factory, address adapter);

contract GetVaultV2MorphoMarketV1Adapter {
function query(IMorphoMarketV1Adapter adapter)
function query(IMorphoMarketV1Adapter adapter, IMorphoMarketV1AdapterFactory factory)
external
view
returns (VaultV2MorphoMarketV1AdapterResponse memory res)
{
if (!factory.isMorphoMarketV1Adapter(address(adapter))) {
revert UnknownOfFactory(address(factory), address(adapter));
}

res.parentVault = adapter.parentVault();
res.skimRecipient = adapter.skimRecipient();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.0;

import {IMorphoMarketV1AdapterV2} from "./interfaces/IMorphoMarketV1AdapterV2.sol";

import {IMorphoMarketV1AdapterV2Factory} from "./interfaces/IMorphoMarketV1AdapterV2Factory.sol";

struct MarketSupplyShares {
bytes32 marketId;
Expand All @@ -16,12 +16,18 @@ struct VaultV2MorphoMarketV1AdapterV2Response {
MarketSupplyShares[] marketSupplyShares;
}

error UnknownOfFactory(address factory, address adapter);

contract GetVaultV2MorphoMarketV1AdapterV2 {
function query(IMorphoMarketV1AdapterV2 adapter)
function query(IMorphoMarketV1AdapterV2 adapter, IMorphoMarketV1AdapterV2Factory factory)
external
view
returns (VaultV2MorphoMarketV1AdapterV2Response memory res)
{
if (!factory.isMorphoMarketV1AdapterV2(address(adapter))) {
revert UnknownOfFactory(address(factory), address(adapter));
}

res.parentVault = adapter.parentVault();
res.skimRecipient = adapter.skimRecipient();
res.adaptiveCurveIrm = adapter.adaptiveCurveIrm();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@
pragma solidity ^0.8.0;

import {IMorphoVaultV1Adapter} from "./interfaces/IMorphoVaultV1Adapter.sol";
import {IMorphoVaultV1AdapterFactory} from "./interfaces/IMorphoVaultV1AdapterFactory.sol";

struct VaultV2MorphoVaultV1AdapterResponse {
address morphoVaultV1;
address parentVault;
address skimRecipient;
}

error UnknownOfFactory(address factory, address adapter);

contract GetVaultV2MorphoVaultV1Adapter {
function query(IMorphoVaultV1Adapter adapter)
function query(IMorphoVaultV1Adapter adapter, IMorphoVaultV1AdapterFactory factory)
external
view
returns (VaultV2MorphoVaultV1AdapterResponse memory res)
{
if (!factory.isMorphoVaultV1Adapter(address(adapter))) {
revert UnknownOfFactory(address(factory), address(adapter));
}

res.morphoVaultV1 = adapter.morphoVaultV1();
res.parentVault = adapter.parentVault();
res.skimRecipient = adapter.skimRecipient();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;

interface IMorphoMarketV1AdapterFactory {
/* EVENTS */

event CreateMorphoMarketV1Adapter(
address indexed parentVault, address indexed morpho, address indexed morphoMarketV1Adapter
);

/* FUNCTIONS */

function morphoMarketV1Adapter(address parentVault, address morpho) external view returns (address);
function isMorphoMarketV1Adapter(address account) external view returns (bool);
function createMorphoMarketV1Adapter(address parentVault, address morpho) external returns (address);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;

interface IVaultV2Factory {
/* EVENTS */

event CreateVaultV2(address indexed owner, address indexed asset, bytes32 salt, address indexed newVaultV2);

/* FUNCTIONS */

function isVaultV2(address account) external view returns (bool);
function vaultV2(address owner, address asset, bytes32 salt) external view returns (address);
function createVaultV2(address owner, address asset, bytes32 salt) external returns (address newVaultV2);
}
18 changes: 18 additions & 0 deletions packages/blue-sdk-viem/src/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { BaseError, ContractFunctionRevertedError } from "viem";

/**
* Checks if an error is a contract revert with the "UnknownOfFactory" error name.
* Used to propagate factory validation errors instead of falling back to multicall.
*/
export function isUnknownOfFactoryError(error: unknown): boolean {
if (!(error instanceof BaseError)) return false;

const revertError = error.walk(
(err) => err instanceof ContractFunctionRevertedError,
);

return (
revertError instanceof ContractFunctionRevertedError &&
revertError.data?.errorName === "UnknownOfFactory"
);
}
52 changes: 45 additions & 7 deletions packages/blue-sdk-viem/src/fetch/Vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ import {
AccrualVault,
Eip5267Domain,
type MarketId,
UnknownFactory,
UnknownOfFactory,
Vault,
VaultConfig,
type VaultPublicAllocatorConfig,
getChainAddresses,
} from "@morpho-org/blue-sdk";

import { getChainId, readContract } from "viem/actions";
import { metaMorphoAbi, publicAllocatorAbi } from "../abis";
import {
metaMorphoAbi,
metaMorphoFactoryAbi,
publicAllocatorAbi,
} from "../abis";
import type { DeploylessFetchParameters } from "../types";
import { fetchVaultMarketAllocation } from "./VaultMarketAllocation";

Expand All @@ -25,7 +31,13 @@ export async function fetchVault(
) {
parameters.chainId ??= await getChainId(client);

const { publicAllocator } = getChainAddresses(parameters.chainId);
const { publicAllocator, metaMorphoFactory } = getChainAddresses(
parameters.chainId,
);

if (!metaMorphoFactory) {
throw new UnknownFactory();
}

if (deployless) {
try {
Expand All @@ -52,7 +64,7 @@ export async function fetchVault(
abi,
code,
functionName: "query",
args: [address, publicAllocator ?? zeroAddress],
args: [address, publicAllocator ?? zeroAddress, metaMorphoFactory],
});

return new Vault({
Expand Down Expand Up @@ -104,6 +116,7 @@ export async function fetchVault(
supplyQueueSize,
withdrawQueueSize,
hasPublicAllocator,
isMetaMorphoV1_1,
] = await Promise.all([
fetchVaultConfig(address, client, { ...parameters, deployless }),
readContract(client, {
Expand Down Expand Up @@ -210,8 +223,28 @@ export async function fetchVault(
functionName: "isAllocator",
args: [publicAllocator],
}),
readContract(client, {
...parameters,
address: metaMorphoFactory,
abi: metaMorphoFactoryAbi,
functionName: "isMetaMorpho",
args: [address],
}).catch(() => false),
]);

// Fallback to the MetaMorphoV1.0 factory on Ethereum (1) and Base (8453)
const isMetaMorphoV1_0Promise =
!isMetaMorphoV1_1 &&
(parameters.chainId === 1 || parameters.chainId === 8453)
? readContract(client, {
...parameters,
address: "0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101",
abi: metaMorphoFactoryAbi,
functionName: "isMetaMorpho",
args: [address],
})
: Promise.resolve(false);

let publicAllocatorConfigPromise:
| Promise<VaultPublicAllocatorConfig>
| undefined;
Expand Down Expand Up @@ -240,8 +273,8 @@ export async function fetchVault(
}),
]).then(([admin, fee, accruedFee]) => ({ admin, fee, accruedFee }));

const [supplyQueue, withdrawQueue, publicAllocatorConfig] = await Promise.all(
[
const [supplyQueue, withdrawQueue, publicAllocatorConfig, isMetaMorphoV1_0] =
await Promise.all([
Promise.all(
Array.from(
{ length: Number(supplyQueueSize) },
Expand Down Expand Up @@ -269,8 +302,13 @@ export async function fetchVault(
),
),
publicAllocatorConfigPromise,
],
);
isMetaMorphoV1_0Promise,
]);

const isMetaMorpho = isMetaMorphoV1_1 || isMetaMorphoV1_0;
if (!isMetaMorpho) {
throw new UnknownOfFactory(metaMorphoFactory, address);
}

return new Vault({
...config,
Expand Down
Loading