@defisaver/positions-sdk 2.1.159 → 2.1.160-morpho-vault-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,43 +11,51 @@ import Dec from 'decimal.js';
11
11
  import { request as graphqlRequest } from 'graphql-request';
12
12
  import { assetAmountInEth } from '@defisaver/tokens';
13
13
  import * as morphoVaultsOptions from './options';
14
+ import { MorphoVaultVersion, } from '../../types';
14
15
  import { getViemProvider } from '../../services/viem';
15
- import { getMorphoVaultContractViem } from '../../contracts';
16
+ import { getErc4626ContractViem, getMorphoVaultContractViem } from '../../contracts';
17
+ import { MORPHO_API_URL } from '../../constants';
16
18
  export { morphoVaultsOptions, };
17
- const MORPHO_BLUE_API = 'https://api.morpho.org/graphql';
18
19
  // Morpho API caps list page size at 100 items
19
20
  const MORPHO_API_PAGE_SIZE = 100;
20
- const vaultsLiquidityQuery = `
21
+ const buildLiquidityQuery = (listField) => `
21
22
  query VaultsLiquidity($addresses: [String!], $chainIds: [Int!]) {
22
- vaults(first: ${MORPHO_API_PAGE_SIZE}, where: { address_in: $addresses, chainId_in: $chainIds }) {
23
+ ${listField}(first: ${MORPHO_API_PAGE_SIZE}, where: { address_in: $addresses, chainId_in: $chainIds }) {
23
24
  items {
24
25
  address
25
- liquidity {
26
- underlying
27
- }
26
+ liquidity${listField === 'vaults' ? ' {\n underlying\n }' : ''}
28
27
  }
29
28
  }
30
29
  }`;
30
+ const liquidityQueries = {
31
+ [MorphoVaultVersion.V1]: { field: 'vaults', query: buildLiquidityQuery('vaults') },
32
+ [MorphoVaultVersion.V2]: { field: 'vaultV2s', query: buildLiquidityQuery('vaultV2s') },
33
+ };
31
34
  /**
32
- * Fetches liquidity for all given vaults in a single API request (chunked if over the page cap),
35
+ * Fetches liquidity for all given vaults, one API request per vault version (chunked if over the page cap),
33
36
  * returned as a map keyed by lowercased vault address. A vault missing from the API response is
34
37
  * absent from the map.
35
38
  */
36
39
  export const fetchMorphoVaultsLiquidity = (network, vaults) => __awaiter(void 0, void 0, void 0, function* () {
37
- const chunks = [];
38
- for (let i = 0; i < vaults.length; i += MORPHO_API_PAGE_SIZE)
39
- chunks.push(vaults.slice(i, i + MORPHO_API_PAGE_SIZE));
40
+ const requests = [];
41
+ [MorphoVaultVersion.V1, MorphoVaultVersion.V2].forEach((version) => {
42
+ const versionVaults = vaults.filter((vault) => vault.version === version);
43
+ for (let i = 0; i < versionVaults.length; i += MORPHO_API_PAGE_SIZE)
44
+ requests.push({ version, chunk: versionVaults.slice(i, i + MORPHO_API_PAGE_SIZE) });
45
+ });
40
46
  const liquidityByAddress = {};
41
- yield Promise.all(chunks.map((chunk) => __awaiter(void 0, void 0, void 0, function* () {
42
- const data = yield graphqlRequest(MORPHO_BLUE_API, vaultsLiquidityQuery, {
47
+ yield Promise.all(requests.map((_a) => __awaiter(void 0, [_a], void 0, function* ({ version, chunk }) {
48
+ const { field, query } = liquidityQueries[version];
49
+ const data = yield graphqlRequest(MORPHO_API_URL, query, {
43
50
  addresses: chunk.map((vault) => vault.address),
44
51
  chainIds: [network],
45
52
  // the BigInt scalar serializes as a JSON number when it fits in Number.MAX_SAFE_INTEGER, a string otherwise
46
53
  });
47
- (data.vaults.items || []).forEach((item) => {
48
- var _a, _b;
49
- if (((_a = item === null || item === void 0 ? void 0 : item.liquidity) === null || _a === void 0 ? void 0 : _a.underlying) !== undefined && ((_b = item.liquidity) === null || _b === void 0 ? void 0 : _b.underlying) !== null) {
50
- liquidityByAddress[item.address.toLowerCase()] = String(item.liquidity.underlying);
54
+ (data[field].items || []).forEach((item) => {
55
+ // V1 exposes liquidity as { underlying }, V2 as a plain BigInt scalar
56
+ const underlying = (item === null || item === void 0 ? void 0 : item.liquidity) !== null && typeof (item === null || item === void 0 ? void 0 : item.liquidity) === 'object' ? item.liquidity.underlying : item === null || item === void 0 ? void 0 : item.liquidity;
57
+ if (underlying !== undefined && underlying !== null) {
58
+ liquidityByAddress[item.address.toLowerCase()] = String(underlying);
51
59
  }
52
60
  });
53
61
  })));
@@ -60,7 +68,19 @@ const getBatchedViemProvider = (provider, network) => getViemProvider(provider,
60
68
  },
61
69
  },
62
70
  });
63
- const getMorphoVaultChainData = (provider, morphoVault, accounts) => __awaiter(void 0, void 0, void 0, function* () {
71
+ // Vault V2 is ERC-4626 but has no decimals offset, so shares are converted with convertToAssets
72
+ const getMorphoVaultV2ChainData = (provider, morphoVault, accounts) => __awaiter(void 0, void 0, void 0, function* () {
73
+ const vaultContract = getErc4626ContractViem(provider, morphoVault.address);
74
+ const [totalAssets, suppliedAssets] = yield Promise.all([
75
+ vaultContract.read.totalAssets(),
76
+ Promise.all(accounts.map((account) => __awaiter(void 0, void 0, void 0, function* () {
77
+ const share = yield vaultContract.read.balanceOf([account]);
78
+ return share === BigInt(0) ? BigInt(0) : vaultContract.read.convertToAssets([share]);
79
+ }))),
80
+ ]);
81
+ return { totalAssets, suppliedAssets };
82
+ });
83
+ const getMorphoVaultV1ChainData = (provider, morphoVault, accounts) => __awaiter(void 0, void 0, void 0, function* () {
64
84
  const morphoVaultContract = getMorphoVaultContractViem(provider, morphoVault.address);
65
85
  const shares = {};
66
86
  const [totalAssets, totalSupply, decimals, decimalsOffset] = yield Promise.all([
@@ -77,11 +97,29 @@ const getMorphoVaultChainData = (provider, morphoVault, accounts) => __awaiter(v
77
97
  totalAssets, totalSupply, decimals, decimalsOffset, shares,
78
98
  };
79
99
  });
100
+ const getMorphoVaultChainData = (provider, morphoVault, accounts) => __awaiter(void 0, void 0, void 0, function* () {
101
+ return (morphoVault.version === MorphoVaultVersion.V2
102
+ ? { v2: yield getMorphoVaultV2ChainData(provider, morphoVault, accounts) }
103
+ : { v1: yield getMorphoVaultV1ChainData(provider, morphoVault, accounts) });
104
+ });
80
105
  const formatMorphoVaultData = (morphoVault, chainData, liquidityUnderlying, accounts) => {
81
- const { totalAssets, totalSupply, decimals, decimalsOffset, shares, } = chainData;
82
- const poolSize = assetAmountInEth(totalAssets.toString(), morphoVault.asset);
83
106
  const liquidity = assetAmountInEth(liquidityUnderlying, morphoVault.asset);
84
107
  const supplied = {};
108
+ if (chainData.v2) {
109
+ const { totalAssets, suppliedAssets } = chainData.v2;
110
+ accounts.forEach((account, i) => {
111
+ supplied[account.toLowerCase()] = assetAmountInEth(suppliedAssets[i].toString(), morphoVault.asset);
112
+ });
113
+ return {
114
+ poolSize: assetAmountInEth(totalAssets.toString(), morphoVault.asset),
115
+ supplied,
116
+ liquidity,
117
+ asset: morphoVault.asset,
118
+ optionType: morphoVault.type,
119
+ };
120
+ }
121
+ const { totalAssets, totalSupply, decimals, decimalsOffset, shares, } = chainData.v1;
122
+ const poolSize = assetAmountInEth(totalAssets.toString(), morphoVault.asset);
85
123
  accounts.forEach((account) => {
86
124
  const share = shares[account] || BigInt(0);
87
125
  supplied[account.toLowerCase()] = new Dec(new Dec(share.toString()).mul(new Dec(totalAssets.toString()).add(1)).div(new Dec(totalSupply.toString()).add(Math.pow(10, decimalsOffset))).div(Math.pow(10, 18))
@@ -17,5 +17,6 @@ export declare const MORPHO_VAULT_STEAKHOUSE_PYUSD: MorphoVault;
17
17
  export declare const MORPHO_VAULT_SMOKEHOUSE_USDT: MorphoVault;
18
18
  export declare const MORPHO_VAULT_SMOKEHOUSE_USDC: MorphoVault;
19
19
  export declare const MORPHO_VAULT_SMOKEHOUSE_DAI: MorphoVault;
20
+ export declare const MORPHO_VAULT_GAUNTLET_USDG_PREMIUM: MorphoVault;
20
21
  export declare const MORPHO_VAULTS: Record<MorphoVaultType, MorphoVault>;
21
22
  export declare const getMorphoVault: (type: MorphoVaultType) => MorphoVault;
@@ -1,9 +1,11 @@
1
- import { MorphoVaultType } from '../../types';
1
+ import { MorphoVaultType, MorphoVaultVersion, NetworkNumber, } from '../../types';
2
2
  export const MORPHO_VAULT_FLAGSHIP_ETH = {
3
3
  type: MorphoVaultType.MorphoVaultFlagshipEth,
4
4
  name: 'Flagship ETH',
5
5
  address: '0x38989BBA00BDF8181F4082995b3DEAe96163aC5D',
6
6
  asset: 'WETH',
7
+ network: NetworkNumber.Eth,
8
+ version: MorphoVaultVersion.V1,
7
9
  deploymentBlock: 18927491,
8
10
  isLegacy: true,
9
11
  };
@@ -12,6 +14,8 @@ export const MORPHO_VAULT_GAUNTLET_USDC_CORE = {
12
14
  name: 'Gauntlet USDC Core',
13
15
  address: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
14
16
  asset: 'USDC',
17
+ network: NetworkNumber.Eth,
18
+ version: MorphoVaultVersion.V1,
15
19
  deploymentBlock: 19777569,
16
20
  isLegacy: true,
17
21
  };
@@ -20,6 +24,8 @@ export const MORPHO_VAULT_GAUNTLET_USDC_PRIME = {
20
24
  name: 'Gauntlet USDC Prime',
21
25
  address: '0xdd0f28e19C1780eb6396170735D45153D261490d',
22
26
  asset: 'USDC',
27
+ network: NetworkNumber.Eth,
28
+ version: MorphoVaultVersion.V1,
23
29
  deploymentBlock: 19777569,
24
30
  isLegacy: true,
25
31
  };
@@ -28,6 +34,8 @@ export const MORPHO_VAULT_RE7_WETH = {
28
34
  name: 'RE7 WETH',
29
35
  address: '0x78Fc2c2eD1A4cDb5402365934aE5648aDAd094d0',
30
36
  asset: 'WETH',
37
+ network: NetworkNumber.Eth,
38
+ version: MorphoVaultVersion.V1,
31
39
  deploymentBlock: 19121118,
32
40
  isLegacy: true,
33
41
  };
@@ -36,6 +44,8 @@ export const MORPHO_VAULT_GAUNTLET_WETH_CORE = {
36
44
  name: 'Gauntlet WETH Core',
37
45
  address: '0x4881Ef0BF6d2365D3dd6499ccd7532bcdBCE0658',
38
46
  asset: 'WETH',
47
+ network: NetworkNumber.Eth,
48
+ version: MorphoVaultVersion.V1,
39
49
  deploymentBlock: 19541126,
40
50
  isLegacy: true,
41
51
  };
@@ -44,6 +54,8 @@ export const MORPHO_VAULT_GAUNTLET_WETH_PRIME = {
44
54
  name: 'Gauntlet WETH Prime',
45
55
  address: '0x2371e134e3455e0593363cBF89d3b6cf53740618',
46
56
  asset: 'WETH',
57
+ network: NetworkNumber.Eth,
58
+ version: MorphoVaultVersion.V1,
47
59
  deploymentBlock: 19372149,
48
60
  isLegacy: true,
49
61
  };
@@ -52,6 +64,8 @@ export const MORPHO_VAULT_BOOSTED_USDC = {
52
64
  name: 'Boosted USDC',
53
65
  address: '0xd63070114470f685b75B74D60EEc7c1113d33a3D',
54
66
  asset: 'USDC',
67
+ network: NetworkNumber.Eth,
68
+ version: MorphoVaultVersion.V1,
55
69
  deploymentBlock: 20377233,
56
70
  isLegacy: true,
57
71
  };
@@ -60,6 +74,8 @@ export const MORPHO_VAULT_FLAGSHIP_USDT = {
60
74
  name: 'Flagship USDT',
61
75
  address: '0x2C25f6C25770fFEC5959D34B94Bf898865e5D6b1',
62
76
  asset: 'USDT',
77
+ network: NetworkNumber.Eth,
78
+ version: MorphoVaultVersion.V1,
63
79
  deploymentBlock: 19127039,
64
80
  isLegacy: true,
65
81
  };
@@ -68,6 +84,8 @@ export const MORPHO_VAULT_GAUNTLET_USDA_CORE = {
68
84
  name: 'Gauntlet USDA Core',
69
85
  address: '0x125D41A6e5dbf455cD9Df8F80BCC6fd172D52Cc6',
70
86
  asset: 'USDA',
87
+ network: NetworkNumber.Eth,
88
+ version: MorphoVaultVersion.V1,
71
89
  deploymentBlock: 19633608,
72
90
  isLegacy: true,
73
91
  };
@@ -76,6 +94,8 @@ export const MORPHO_VAULT_GAUNTLET_USDT_PRIME = {
76
94
  name: 'Gauntlet USDT Prime',
77
95
  address: '0x8CB3649114051cA5119141a34C200D65dc0Faa73',
78
96
  asset: 'USDT',
97
+ network: NetworkNumber.Eth,
98
+ version: MorphoVaultVersion.V1,
79
99
  deploymentBlock: 19486541,
80
100
  isLegacy: true,
81
101
  };
@@ -84,6 +104,8 @@ export const MORPHO_VAULT_GAUNTLET_RESOLV_USDC = {
84
104
  name: 'Gauntlet Resolv USDC',
85
105
  address: '0x132E6C9C33A62D7727cd359b1f51e5B566E485Eb',
86
106
  asset: 'USDC',
107
+ network: NetworkNumber.Eth,
108
+ version: MorphoVaultVersion.V1,
87
109
  deploymentBlock: 21487882,
88
110
  isLegacy: true,
89
111
  };
@@ -93,6 +115,8 @@ export const MORPHO_VAULT_STEAKHOUSE_USDT = {
93
115
  name: 'Steakhouse USDT',
94
116
  address: '0xbEef047a543E45807105E51A8BBEFCc5950fcfBa',
95
117
  asset: 'USDT',
118
+ network: NetworkNumber.Eth,
119
+ version: MorphoVaultVersion.V1,
96
120
  deploymentBlock: 19043398,
97
121
  isLegacy: false,
98
122
  };
@@ -101,6 +125,8 @@ export const MORPHO_VAULT_STEAKHOUSE_USDC = {
101
125
  name: 'Steakhouse USDC',
102
126
  address: '0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB',
103
127
  asset: 'USDC',
128
+ network: NetworkNumber.Eth,
129
+ version: MorphoVaultVersion.V1,
104
130
  deploymentBlock: 18928285,
105
131
  isLegacy: false,
106
132
  };
@@ -109,6 +135,8 @@ export const MORPHO_VAULT_STEAKHOUSE_ETH = {
109
135
  name: 'Steakhouse WETH',
110
136
  address: '0xBEEf050ecd6a16c4e7bfFbB52Ebba7846C4b8cD4',
111
137
  asset: 'WETH',
138
+ network: NetworkNumber.Eth,
139
+ version: MorphoVaultVersion.V1,
112
140
  deploymentBlock: 19380443,
113
141
  isLegacy: false,
114
142
  };
@@ -117,6 +145,8 @@ export const MORPHO_VAULT_STEAKHOUSE_PYUSD = {
117
145
  name: 'Steakhouse PYUSD',
118
146
  address: '0xbEEF02e5E13584ab96848af90261f0C8Ee04722a',
119
147
  asset: 'PYUSD',
148
+ network: NetworkNumber.Eth,
149
+ version: MorphoVaultVersion.V1,
120
150
  deploymentBlock: 19043398,
121
151
  isLegacy: false,
122
152
  };
@@ -126,6 +156,8 @@ export const MORPHO_VAULT_SMOKEHOUSE_USDT = {
126
156
  name: 'Smokehouse USDT',
127
157
  address: '0xA0804346780b4c2e3bE118ac957D1DB82F9d7484',
128
158
  asset: 'USDT',
159
+ network: NetworkNumber.Eth,
160
+ version: MorphoVaultVersion.V1,
129
161
  deploymentBlock: 21522238,
130
162
  isLegacy: false,
131
163
  };
@@ -134,6 +166,8 @@ export const MORPHO_VAULT_SMOKEHOUSE_USDC = {
134
166
  name: 'Smokehouse USDC',
135
167
  address: '0xBEeFFF209270748ddd194831b3fa287a5386f5bC',
136
168
  asset: 'USDC',
169
+ network: NetworkNumber.Eth,
170
+ version: MorphoVaultVersion.V1,
137
171
  deploymentBlock: 21337719,
138
172
  isLegacy: false,
139
173
  };
@@ -142,9 +176,22 @@ export const MORPHO_VAULT_SMOKEHOUSE_DAI = {
142
176
  name: 'Smokehouse DAI',
143
177
  address: '0xbeeFfF68CC520D68f82641EFF84330C631E2490E',
144
178
  asset: 'DAI',
179
+ network: NetworkNumber.Eth,
180
+ version: MorphoVaultVersion.V1,
145
181
  deploymentBlock: 21343808,
146
182
  isLegacy: false,
147
183
  };
184
+ // Vault V2
185
+ export const MORPHO_VAULT_GAUNTLET_USDG_PREMIUM = {
186
+ type: MorphoVaultType.MorphoVaultGauntletUSDGPremium,
187
+ name: 'Gauntlet USDG Premium',
188
+ address: '0x390c1bb01F3F627144a40617e287d4cE3D5aBCfa',
189
+ asset: 'USDG',
190
+ network: NetworkNumber.Arb,
191
+ version: MorphoVaultVersion.V2,
192
+ deploymentBlock: 510799903,
193
+ isLegacy: false,
194
+ };
148
195
  export const MORPHO_VAULTS = {
149
196
  [MorphoVaultType.MorphoVaultFlagshipEth]: MORPHO_VAULT_FLAGSHIP_ETH,
150
197
  [MorphoVaultType.MorphoVaultGauntletUSDCCore]: MORPHO_VAULT_GAUNTLET_USDC_CORE,
@@ -166,5 +213,7 @@ export const MORPHO_VAULTS = {
166
213
  [MorphoVaultType.MorphoVaultSmokehouseUSDT]: MORPHO_VAULT_SMOKEHOUSE_USDT,
167
214
  [MorphoVaultType.MorphoVaultSmokehouseUSDC]: MORPHO_VAULT_SMOKEHOUSE_USDC,
168
215
  [MorphoVaultType.MorphoVaultSmokehouseDAI]: MORPHO_VAULT_SMOKEHOUSE_DAI,
216
+ // Vault V2
217
+ [MorphoVaultType.MorphoVaultGauntletUSDGPremium]: MORPHO_VAULT_GAUNTLET_USDG_PREMIUM,
169
218
  };
170
219
  export const getMorphoVault = (type) => MORPHO_VAULTS[type];
@@ -1,4 +1,4 @@
1
- import { EthAddress } from '../common';
1
+ import { EthAddress, NetworkNumber } from '../common';
2
2
  export declare enum MorphoVaultType {
3
3
  MorphoVaultFlagshipEth = "morpho_vault_flagship_eth",
4
4
  MorphoVaultGauntletUSDCCore = "morpho_vault_gauntlet_usdc_core",
@@ -17,13 +17,20 @@ export declare enum MorphoVaultType {
17
17
  MorphoVaultSteakhousePYUSD = "morpho_vault_steakhouse_pyusd",
18
18
  MorphoVaultSmokehouseUSDT = "morpho_vault_smokehouse_usdt",
19
19
  MorphoVaultSmokehouseUSDC = "morpho_vault_smokehouse_usdc",
20
- MorphoVaultSmokehouseDAI = "morpho_vault_smokehouse_dai"
20
+ MorphoVaultSmokehouseDAI = "morpho_vault_smokehouse_dai",
21
+ MorphoVaultGauntletUSDGPremium = "morpho_vault_gauntlet_usdg_premium"
22
+ }
23
+ export declare enum MorphoVaultVersion {
24
+ V1 = 1,
25
+ V2 = 2
21
26
  }
22
27
  export interface MorphoVault {
23
28
  type: MorphoVaultType;
24
29
  name: string;
25
30
  address: EthAddress;
26
31
  asset: string;
32
+ network: NetworkNumber;
33
+ version: MorphoVaultVersion;
27
34
  deploymentBlock: number;
28
35
  isLegacy: boolean;
29
36
  }
@@ -20,4 +20,11 @@ export var MorphoVaultType;
20
20
  MorphoVaultType["MorphoVaultSmokehouseUSDT"] = "morpho_vault_smokehouse_usdt";
21
21
  MorphoVaultType["MorphoVaultSmokehouseUSDC"] = "morpho_vault_smokehouse_usdc";
22
22
  MorphoVaultType["MorphoVaultSmokehouseDAI"] = "morpho_vault_smokehouse_dai";
23
+ // Vault V2
24
+ MorphoVaultType["MorphoVaultGauntletUSDGPremium"] = "morpho_vault_gauntlet_usdg_premium";
23
25
  })(MorphoVaultType || (MorphoVaultType = {}));
26
+ export var MorphoVaultVersion;
27
+ (function (MorphoVaultVersion) {
28
+ MorphoVaultVersion[MorphoVaultVersion["V1"] = 1] = "V1";
29
+ MorphoVaultVersion[MorphoVaultVersion["V2"] = 2] = "V2";
30
+ })(MorphoVaultVersion || (MorphoVaultVersion = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/positions-sdk",
3
- "version": "2.1.159",
3
+ "version": "2.1.160-morpho-vault-dev",
4
4
  "description": "",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -9,4 +9,8 @@ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
9
9
  export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
10
10
  export const WAD = 1e18;
11
11
  export const USD_QUOTE = '0x0000000000000000000000000000000000000348';
12
- export const borrowOperations = ['borrow', 'payback'];
12
+ export const borrowOperations = ['borrow', 'payback'];
13
+
14
+ // Proxies Morpho's GraphQL API through the DFS backend, which attaches the private API key
15
+ // for better rate limits (DEV-13673)
16
+ export const MORPHO_API_URL = 'https://fe.defisaver.com/api/morpho/graphql';
@@ -12,7 +12,9 @@ import {
12
12
  MorphoBluePublicAllocatorItem,
13
13
  MorphoBlueRealloactionMarketData,
14
14
  } from '../../types';
15
- import { borrowOperations, SECONDS_PER_YEAR, WAD } from '../../constants';
15
+ import {
16
+ borrowOperations, MORPHO_API_URL, SECONDS_PER_YEAR, WAD,
17
+ } from '../../constants';
16
18
  import { MorphoBlueViewContractViem } from '../../contracts';
17
19
  import { compareAddresses, LONGER_TIMEOUT, wethToEth } from '../../services/utils';
18
20
  import { getViemProvider } from '../../services/viem';
@@ -147,7 +149,6 @@ export const getApyAfterValuesEstimation = async (selectedMarket: MorphoBlueMark
147
149
  return { borrowRate, supplyRate };
148
150
  };
149
151
 
150
- const API_URL = 'https://api.morpho.org/graphql';
151
152
  // Morpho Blue ACRM (Adaptive Curve IRM) always targets 90% utilization — protocol constant
152
153
  const ACRM_TARGET_UTILIZATION = '900000000000000000';
153
154
 
@@ -226,7 +227,7 @@ const REWARDS_QUERY = `
226
227
  */
227
228
  export const getReallocatableLiquidity = async (marketId: string, network: NetworkNumber = NetworkNumber.Eth): Promise<{ reallocatableLiquidity: string, targetBorrowUtilization: string }> => {
228
229
  try {
229
- const response = await fetch(API_URL, {
230
+ const response = await fetch(MORPHO_API_URL, {
230
231
  method: 'POST',
231
232
  headers: { 'Content-Type': 'application/json' },
232
233
  body: JSON.stringify({
@@ -291,7 +292,7 @@ export const getLiquidityToAllocate = (amountToBorrow: string, totalBorrow: stri
291
292
  export const getReallocation = async (market: MorphoBlueMarketData, assetsData: MorphoBlueAssetsData, amountToBorrow: string, network: NetworkNumber = NetworkNumber.Eth): Promise<{ vaults: string[], withdrawals: (string | string[])[][][] }> => {
292
293
  try {
293
294
  const { marketId, loanToken } = market;
294
- const response = await fetch(API_URL, {
295
+ const response = await fetch(MORPHO_API_URL, {
295
296
  method: 'POST',
296
297
  headers: { 'Content-Type': 'application/json' },
297
298
  body: JSON.stringify({
@@ -382,7 +383,7 @@ export const getReallocation = async (market: MorphoBlueMarketData, assetsData:
382
383
  };
383
384
 
384
385
  export const getRewardsForMarket = async (marketId: string, network: NetworkNumber = NetworkNumber.Eth) => {
385
- const response = await fetch(API_URL, {
386
+ const response = await fetch(MORPHO_API_URL, {
386
387
  method: 'POST',
387
388
  headers: { 'Content-Type': 'application/json' },
388
389
  body: JSON.stringify({
@@ -32,7 +32,8 @@ export const getSavingsData = async (
32
32
  network: NetworkNumber,
33
33
  accounts: EthAddress[],
34
34
  ) => {
35
- const morphoVaultsList = Object.keys(morphoVaults.morphoVaultsOptions.MORPHO_VAULTS) as MorphoVaultType[];
35
+ const morphoVaultsList = (Object.keys(morphoVaults.morphoVaultsOptions.MORPHO_VAULTS) as MorphoVaultType[])
36
+ .filter((key) => morphoVaults.morphoVaultsOptions.getMorphoVault(key).network === network);
36
37
  const yearnVaultsList = Object.keys(yearnVaults.yearnVaultsOptions.YEARN_VAULTS) as YearnVaultType[];
37
38
  const sparkSavingsVaultsList = Object.keys(sparkSavingsVaults.sparkSavingsVaultsOptions.SPARK_SAVINGS_VAULTS) as SparkSavingsVaultType[];
38
39
  const yearnV3VaultsList = Object.keys(yearnV3Vaults.yearnV3VaultsOptions.YEARN_V3_VAULTS) as YearnV3VaultType[];
@@ -42,7 +43,7 @@ export const getSavingsData = async (
42
43
  const savingsData: SavingsData = {};
43
44
 
44
45
  await Promise.all([
45
- ...(network === NetworkNumber.Eth ? [
46
+ ...(morphoVaultsList.length ? [
46
47
  (async () => {
47
48
  try {
48
49
  const vaults = morphoVaultsList.map((vaultKey) => morphoVaults.morphoVaultsOptions.getMorphoVault(vaultKey));
@@ -52,6 +53,8 @@ export const getSavingsData = async (
52
53
  console.error('[getSavingsData] Error fetching morpho vaults:', err);
53
54
  }
54
55
  })(),
56
+ ] : []),
57
+ ...(network === NetworkNumber.Eth ? [
55
58
  ...yearnVaultsList.map(async (vaultKey) => {
56
59
  try {
57
60
  const vault = yearnVaults.yearnVaultsOptions.getYearnVault(vaultKey);
@@ -4,52 +4,62 @@ import { request as graphqlRequest } from 'graphql-request';
4
4
  import { assetAmountInEth } from '@defisaver/tokens';
5
5
  import * as morphoVaultsOptions from './options';
6
6
  import {
7
- EthAddress, EthereumProvider, NetworkNumber, MorphoVault, MorphoVaultType, SavingsVaultData,
7
+ EthAddress, EthereumProvider, NetworkNumber, MorphoVault, MorphoVaultType, MorphoVaultVersion, SavingsVaultData,
8
8
  } from '../../types';
9
9
  import { getViemProvider } from '../../services/viem';
10
- import { getMorphoVaultContractViem } from '../../contracts';
10
+ import { getErc4626ContractViem, getMorphoVaultContractViem } from '../../contracts';
11
+ import { MORPHO_API_URL } from '../../constants';
11
12
 
12
13
  export {
13
14
  morphoVaultsOptions,
14
15
  };
15
16
 
16
- const MORPHO_BLUE_API = 'https://api.morpho.org/graphql';
17
-
18
17
  // Morpho API caps list page size at 100 items
19
18
  const MORPHO_API_PAGE_SIZE = 100;
20
19
 
21
- const vaultsLiquidityQuery = `
20
+ const buildLiquidityQuery = (listField: 'vaults' | 'vaultV2s') => `
22
21
  query VaultsLiquidity($addresses: [String!], $chainIds: [Int!]) {
23
- vaults(first: ${MORPHO_API_PAGE_SIZE}, where: { address_in: $addresses, chainId_in: $chainIds }) {
22
+ ${listField}(first: ${MORPHO_API_PAGE_SIZE}, where: { address_in: $addresses, chainId_in: $chainIds }) {
24
23
  items {
25
24
  address
26
- liquidity {
27
- underlying
28
- }
25
+ liquidity${listField === 'vaults' ? ' {\n underlying\n }' : ''}
29
26
  }
30
27
  }
31
28
  }`;
32
29
 
30
+ const liquidityQueries = {
31
+ [MorphoVaultVersion.V1]: { field: 'vaults', query: buildLiquidityQuery('vaults') },
32
+ [MorphoVaultVersion.V2]: { field: 'vaultV2s', query: buildLiquidityQuery('vaultV2s') },
33
+ } as const;
34
+
35
+ type LiquidityItem = { address: string, liquidity: { underlying: string | number | null } | string | number | null };
36
+
33
37
  /**
34
- * Fetches liquidity for all given vaults in a single API request (chunked if over the page cap),
38
+ * Fetches liquidity for all given vaults, one API request per vault version (chunked if over the page cap),
35
39
  * returned as a map keyed by lowercased vault address. A vault missing from the API response is
36
40
  * absent from the map.
37
41
  */
38
42
  export const fetchMorphoVaultsLiquidity = async (network: NetworkNumber, vaults: MorphoVault[]): Promise<Record<string, string>> => {
39
- const chunks: MorphoVault[][] = [];
40
- for (let i = 0; i < vaults.length; i += MORPHO_API_PAGE_SIZE) chunks.push(vaults.slice(i, i + MORPHO_API_PAGE_SIZE));
43
+ const requests: { version: MorphoVaultVersion, chunk: MorphoVault[] }[] = [];
44
+ [MorphoVaultVersion.V1, MorphoVaultVersion.V2].forEach((version) => {
45
+ const versionVaults = vaults.filter((vault) => vault.version === version);
46
+ for (let i = 0; i < versionVaults.length; i += MORPHO_API_PAGE_SIZE) requests.push({ version, chunk: versionVaults.slice(i, i + MORPHO_API_PAGE_SIZE) });
47
+ });
41
48
 
42
49
  const liquidityByAddress: Record<string, string> = {};
43
- await Promise.all(chunks.map(async (chunk) => {
44
- const data = await graphqlRequest(MORPHO_BLUE_API, vaultsLiquidityQuery, {
50
+ await Promise.all(requests.map(async ({ version, chunk }) => {
51
+ const { field, query } = liquidityQueries[version];
52
+ const data = await graphqlRequest(MORPHO_API_URL, query, {
45
53
  addresses: chunk.map((vault) => vault.address),
46
54
  chainIds: [network],
47
55
  // the BigInt scalar serializes as a JSON number when it fits in Number.MAX_SAFE_INTEGER, a string otherwise
48
- }) as { vaults: { items: { address: string, liquidity: { underlying: string | number | null } | null }[] | null } };
56
+ }) as Record<string, { items: LiquidityItem[] | null }>;
49
57
 
50
- (data.vaults.items || []).forEach((item) => {
51
- if (item?.liquidity?.underlying !== undefined && item.liquidity?.underlying !== null) {
52
- liquidityByAddress[item.address.toLowerCase()] = String(item.liquidity.underlying);
58
+ (data[field].items || []).forEach((item) => {
59
+ // V1 exposes liquidity as { underlying }, V2 as a plain BigInt scalar
60
+ const underlying = item?.liquidity !== null && typeof item?.liquidity === 'object' ? item.liquidity.underlying : item?.liquidity;
61
+ if (underlying !== undefined && underlying !== null) {
62
+ liquidityByAddress[item.address.toLowerCase()] = String(underlying);
53
63
  }
54
64
  });
55
65
  }));
@@ -64,7 +74,22 @@ const getBatchedViemProvider = (provider: EthereumProvider, network: NetworkNumb
64
74
  },
65
75
  });
66
76
 
67
- const getMorphoVaultChainData = async (provider: Client, morphoVault: MorphoVault, accounts: EthAddress[]) => {
77
+ // Vault V2 is ERC-4626 but has no decimals offset, so shares are converted with convertToAssets
78
+ const getMorphoVaultV2ChainData = async (provider: Client, morphoVault: MorphoVault, accounts: EthAddress[]) => {
79
+ const vaultContract = getErc4626ContractViem(provider, morphoVault.address);
80
+
81
+ const [totalAssets, suppliedAssets] = await Promise.all([
82
+ vaultContract.read.totalAssets(),
83
+ Promise.all(accounts.map(async (account) => {
84
+ const share = await vaultContract.read.balanceOf([account]);
85
+ return share === BigInt(0) ? BigInt(0) : vaultContract.read.convertToAssets([share]);
86
+ })),
87
+ ]);
88
+
89
+ return { totalAssets, suppliedAssets };
90
+ };
91
+
92
+ const getMorphoVaultV1ChainData = async (provider: Client, morphoVault: MorphoVault, accounts: EthAddress[]) => {
68
93
  const morphoVaultContract = getMorphoVaultContractViem(provider, morphoVault.address);
69
94
 
70
95
  const shares: Record<EthAddress, bigint> = {};
@@ -85,20 +110,46 @@ const getMorphoVaultChainData = async (provider: Client, morphoVault: MorphoVaul
85
110
  };
86
111
  };
87
112
 
113
+ type MorphoVaultChainData = {
114
+ v1?: Awaited<ReturnType<typeof getMorphoVaultV1ChainData>>,
115
+ v2?: Awaited<ReturnType<typeof getMorphoVaultV2ChainData>>,
116
+ };
117
+
118
+ const getMorphoVaultChainData = async (provider: Client, morphoVault: MorphoVault, accounts: EthAddress[]): Promise<MorphoVaultChainData> => (
119
+ morphoVault.version === MorphoVaultVersion.V2
120
+ ? { v2: await getMorphoVaultV2ChainData(provider, morphoVault, accounts) }
121
+ : { v1: await getMorphoVaultV1ChainData(provider, morphoVault, accounts) }
122
+ );
123
+
88
124
  const formatMorphoVaultData = (
89
125
  morphoVault: MorphoVault,
90
- chainData: Awaited<ReturnType<typeof getMorphoVaultChainData>>,
126
+ chainData: MorphoVaultChainData,
91
127
  liquidityUnderlying: string,
92
128
  accounts: EthAddress[],
93
129
  ): SavingsVaultData => {
130
+ const liquidity = assetAmountInEth(liquidityUnderlying, morphoVault.asset);
131
+ const supplied: Record<EthAddress, string> = {};
132
+
133
+ if (chainData.v2) {
134
+ const { totalAssets, suppliedAssets } = chainData.v2;
135
+ accounts.forEach((account, i) => {
136
+ supplied[account.toLowerCase() as EthAddress] = assetAmountInEth(suppliedAssets[i].toString(), morphoVault.asset);
137
+ });
138
+ return {
139
+ poolSize: assetAmountInEth(totalAssets.toString(), morphoVault.asset),
140
+ supplied,
141
+ liquidity,
142
+ asset: morphoVault.asset,
143
+ optionType: morphoVault.type,
144
+ };
145
+ }
146
+
94
147
  const {
95
148
  totalAssets, totalSupply, decimals, decimalsOffset, shares,
96
- } = chainData;
149
+ } = chainData.v1!;
97
150
 
98
151
  const poolSize = assetAmountInEth(totalAssets.toString(), morphoVault.asset);
99
- const liquidity = assetAmountInEth(liquidityUnderlying, morphoVault.asset);
100
152
 
101
- const supplied: Record<EthAddress, string> = {};
102
153
  accounts.forEach((account) => {
103
154
  const share = shares[account] || BigInt(0);
104
155
  supplied[account.toLowerCase() as EthAddress] = new Dec(new Dec(share.toString()).mul(new Dec(totalAssets.toString()).add(1)).div(new Dec(totalSupply.toString()).add(10 ** decimalsOffset)).div(10 ** 18)