@continuumdao/ctm-mpc-defi 0.2.9 → 0.2.11
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.
- package/dist/agent/catalog.cjs +522 -19
- package/dist/agent/catalog.cjs.map +1 -1
- package/dist/agent/catalog.d.ts +639 -14
- package/dist/agent/catalog.js +510 -20
- package/dist/agent/catalog.js.map +1 -1
- package/dist/agent/skills/hyperliquid/SKILL.md +34 -6
- package/dist/agent/skills/morpho/SKILL.md +63 -0
- package/dist/core/index.cjs +9 -0
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.js +8 -1
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +9 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/protocols/evm/aave-v4/index.cjs.map +1 -1
- package/dist/protocols/evm/aave-v4/index.js.map +1 -1
- package/dist/protocols/evm/euler-v2/index.cjs.map +1 -1
- package/dist/protocols/evm/euler-v2/index.js.map +1 -1
- package/dist/protocols/evm/hyperliquid/index.cjs +407 -34
- package/dist/protocols/evm/hyperliquid/index.cjs.map +1 -1
- package/dist/protocols/evm/hyperliquid/index.d.ts +168 -15
- package/dist/protocols/evm/hyperliquid/index.js +390 -35
- package/dist/protocols/evm/hyperliquid/index.js.map +1 -1
- package/dist/protocols/evm/maple/index.cjs.map +1 -1
- package/dist/protocols/evm/maple/index.js.map +1 -1
- package/dist/protocols/evm/morpho/index.cjs +1971 -0
- package/dist/protocols/evm/morpho/index.cjs.map +1 -0
- package/dist/protocols/evm/morpho/index.d.ts +522 -0
- package/dist/protocols/evm/morpho/index.js +1918 -0
- package/dist/protocols/evm/morpho/index.js.map +1 -0
- package/dist/protocols/evm/sky/index.cjs.map +1 -1
- package/dist/protocols/evm/sky/index.js.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,1971 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var viem = require('viem');
|
|
4
|
+
var continuumNodeSdk = require('@continuumdao/continuum-node-sdk');
|
|
5
|
+
|
|
6
|
+
// src/core/registry.ts
|
|
7
|
+
var modules = [];
|
|
8
|
+
function registerProtocolModule(mod) {
|
|
9
|
+
const existing = modules.findIndex((m) => m.id === mod.id);
|
|
10
|
+
if (existing >= 0) {
|
|
11
|
+
modules[existing] = mod;
|
|
12
|
+
} else {
|
|
13
|
+
modules.push(mod);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function postJsonViaOptionalProxy(args) {
|
|
17
|
+
const r = await fetch(args.directUrl, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "content-type": "application/json" },
|
|
20
|
+
body: JSON.stringify(args.body)
|
|
21
|
+
});
|
|
22
|
+
if (!r.ok) {
|
|
23
|
+
const t = await r.text().catch(() => "");
|
|
24
|
+
throw new Error(t ? `HTTP ${r.status}: ${t.slice(0, 200)}` : `HTTP ${r.status}`);
|
|
25
|
+
}
|
|
26
|
+
return await r.json();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/protocols/evm/morpho/api.ts
|
|
30
|
+
var MORPHO_GRAPHQL_URL = "https://api.morpho.org/graphql";
|
|
31
|
+
async function morphoGql(query, variables) {
|
|
32
|
+
const body = { query, variables: variables ?? {} };
|
|
33
|
+
const j = await postJsonViaOptionalProxy({
|
|
34
|
+
directUrl: MORPHO_GRAPHQL_URL,
|
|
35
|
+
body});
|
|
36
|
+
if (j.errors?.length) {
|
|
37
|
+
const msg = j.errors.map((e) => e.message ?? "Unknown").join("; ");
|
|
38
|
+
throw new Error(msg);
|
|
39
|
+
}
|
|
40
|
+
if (j.data == null) throw new Error("Morpho API: empty response");
|
|
41
|
+
return j.data;
|
|
42
|
+
}
|
|
43
|
+
async function fetchMorphoChains() {
|
|
44
|
+
const d = await morphoGql(`
|
|
45
|
+
query { chains { id network } }
|
|
46
|
+
`);
|
|
47
|
+
return d.chains ?? [];
|
|
48
|
+
}
|
|
49
|
+
async function loadMorphoSupportedChainIds() {
|
|
50
|
+
const rows = await fetchMorphoChains();
|
|
51
|
+
const s = /* @__PURE__ */ new Set();
|
|
52
|
+
for (const c of rows) {
|
|
53
|
+
if (typeof c.id === "number" && Number.isFinite(c.id)) s.add(c.id);
|
|
54
|
+
}
|
|
55
|
+
return s;
|
|
56
|
+
}
|
|
57
|
+
function morphoKeyForAssetRow(args) {
|
|
58
|
+
const raw = (args.contractAddress ?? "").trim();
|
|
59
|
+
if (!raw) return null;
|
|
60
|
+
const lower = raw.toLowerCase();
|
|
61
|
+
if (lower === "0x0000000000000000000000000000000000000000" || lower === "native") {
|
|
62
|
+
const w = (args.nativeWrapped ?? "").trim();
|
|
63
|
+
if (!w || !viem.isAddress(w)) return null;
|
|
64
|
+
return viem.getAddress(w).toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
if (!viem.isAddress(raw)) return null;
|
|
67
|
+
return viem.getAddress(raw).toLowerCase();
|
|
68
|
+
}
|
|
69
|
+
function formatMorphoApyPct(apy) {
|
|
70
|
+
if (apy == null || !Number.isFinite(apy)) return "\u2014";
|
|
71
|
+
return `${(apy * 100).toFixed(2)}%`;
|
|
72
|
+
}
|
|
73
|
+
function formatMorphoFeePct(fee) {
|
|
74
|
+
if (fee == null || !Number.isFinite(fee)) return "\u2014";
|
|
75
|
+
return `${(fee * 100).toFixed(2)}%`;
|
|
76
|
+
}
|
|
77
|
+
function formatMorphoUsd(n) {
|
|
78
|
+
if (n == null || !Number.isFinite(n)) return "\u2014";
|
|
79
|
+
if (n >= 1e6) return `$${(n / 1e6).toFixed(2)}M`;
|
|
80
|
+
if (n >= 1e3) return `$${(n / 1e3).toFixed(2)}K`;
|
|
81
|
+
return `$${n.toFixed(2)}`;
|
|
82
|
+
}
|
|
83
|
+
function marketParamsFromApiRow(row) {
|
|
84
|
+
const loanToken = viem.getAddress(row.loanAsset.address ?? "");
|
|
85
|
+
const collateralToken = viem.getAddress(row.collateralAsset.address ?? "");
|
|
86
|
+
const oracle = viem.getAddress(row.oracle.address);
|
|
87
|
+
const irm = viem.getAddress(row.irmAddress);
|
|
88
|
+
const lltv = BigInt(row.lltv);
|
|
89
|
+
return { loanToken, collateralToken, oracle, irm, lltv };
|
|
90
|
+
}
|
|
91
|
+
async function fetchMorphoMarketById(args) {
|
|
92
|
+
const marketId = args.marketId.trim();
|
|
93
|
+
if (!marketId) return null;
|
|
94
|
+
const d = await morphoGql(
|
|
95
|
+
`
|
|
96
|
+
query MorphoMarketById($marketId: String!, $chainId: Int!) {
|
|
97
|
+
marketById(marketId: $marketId, chainId: $chainId) {
|
|
98
|
+
marketId
|
|
99
|
+
loanAsset { address symbol decimals price { usd } }
|
|
100
|
+
collateralAsset { address symbol decimals price { usd } }
|
|
101
|
+
lltv
|
|
102
|
+
oracle { address }
|
|
103
|
+
irmAddress
|
|
104
|
+
morphoBlue { address }
|
|
105
|
+
state {
|
|
106
|
+
supplyApy borrowApy netSupplyApy netBorrowApy
|
|
107
|
+
supplyAssetsUsd borrowAssetsUsd collateralAssetsUsd utilization
|
|
108
|
+
rewards { supplyApr borrowApr asset { address symbol } }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
`,
|
|
113
|
+
{ marketId, chainId: args.chainId }
|
|
114
|
+
);
|
|
115
|
+
const row = d.marketById;
|
|
116
|
+
if (!row?.marketId || !row.morphoBlue?.address) return null;
|
|
117
|
+
return {
|
|
118
|
+
...row,
|
|
119
|
+
morphoBlueAddress: viem.getAddress(row.morphoBlue.address),
|
|
120
|
+
marketParams: marketParamsFromApiRow(row)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
var LIGHT_BATCH_SIZE = 8;
|
|
124
|
+
var V2_EXPOSURE_POSITION_LIMIT = 20;
|
|
125
|
+
var EXPOSURE_FETCH_CONCURRENCY = 4;
|
|
126
|
+
function formatAllocationPercent(partUsd, totalUsd) {
|
|
127
|
+
if (!Number.isFinite(partUsd) || !Number.isFinite(totalUsd) || totalUsd <= 0) return "\u2014";
|
|
128
|
+
return `${(partUsd / totalUsd * 100).toFixed(1)}%`;
|
|
129
|
+
}
|
|
130
|
+
function marketExposureLabel(loan, collateral) {
|
|
131
|
+
const loanSym = (loan ?? "").trim() || "LOAN";
|
|
132
|
+
const colSym = (collateral ?? "").trim();
|
|
133
|
+
return colSym ? `${loanSym}/${colSym}` : loanSym;
|
|
134
|
+
}
|
|
135
|
+
function latestHistoryApy(points) {
|
|
136
|
+
const y = points?.[0]?.y;
|
|
137
|
+
return y != null && Number.isFinite(y) ? y : null;
|
|
138
|
+
}
|
|
139
|
+
function parseV1Exposure(allocation, totalUsd) {
|
|
140
|
+
const out = [];
|
|
141
|
+
for (const a of allocation) {
|
|
142
|
+
const usd = a.supplyAssetsUsd;
|
|
143
|
+
if (usd == null || !Number.isFinite(usd) || usd <= 0) continue;
|
|
144
|
+
out.push({
|
|
145
|
+
label: marketExposureLabel(a.market?.loanAsset?.symbol, a.market?.collateralAsset?.symbol),
|
|
146
|
+
allocatedUsdLabel: formatMorphoUsd(usd),
|
|
147
|
+
allocationPercentLabel: formatAllocationPercent(usd, totalUsd)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return out.sort((a, b) => parseFloat(b.allocatedUsdLabel.replace(/[^0-9.]/g, "")) - parseFloat(a.allocatedUsdLabel.replace(/[^0-9.]/g, "")));
|
|
151
|
+
}
|
|
152
|
+
function parseV2Exposure(adapters, totalUsd) {
|
|
153
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const adapter of adapters) {
|
|
155
|
+
if (adapter.__typename === "MorphoVaultV2Adapter") {
|
|
156
|
+
const usd = adapter.assetsUsd;
|
|
157
|
+
if (usd == null || !Number.isFinite(usd) || usd <= 0) continue;
|
|
158
|
+
const label = (adapter.innerVault?.name ?? adapter.innerVault?.asset?.symbol ?? "Vault V2 adapter").trim() || "Vault V2 adapter";
|
|
159
|
+
byKey.set(label, (byKey.get(label) ?? 0) + usd);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (adapter.__typename === "MorphoMarketV1Adapter") {
|
|
163
|
+
for (const p of adapter.positions?.items ?? []) {
|
|
164
|
+
const usd = p.state?.supplyAssetsUsd;
|
|
165
|
+
if (usd == null || !Number.isFinite(usd) || usd <= 0) continue;
|
|
166
|
+
const label = marketExposureLabel(p.market?.loanAsset?.symbol, p.market?.collateralAsset?.symbol);
|
|
167
|
+
byKey.set(label, (byKey.get(label) ?? 0) + usd);
|
|
168
|
+
}
|
|
169
|
+
const adapterUsd = adapter.assetsUsd;
|
|
170
|
+
if (!adapter.positions?.items?.length && adapterUsd != null && Number.isFinite(adapterUsd) && adapterUsd > 0) {
|
|
171
|
+
byKey.set("Morpho markets", (byKey.get("Morpho markets") ?? 0) + adapterUsd);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return [...byKey.entries()].map(([label, usd]) => ({
|
|
176
|
+
label,
|
|
177
|
+
allocatedUsdLabel: formatMorphoUsd(usd),
|
|
178
|
+
allocationPercentLabel: formatAllocationPercent(usd, totalUsd)
|
|
179
|
+
})).sort((a, b) => parseFloat(b.allocatedUsdLabel.replace(/[^0-9.]/g, "")) - parseFloat(a.allocatedUsdLabel.replace(/[^0-9.]/g, "")));
|
|
180
|
+
}
|
|
181
|
+
function emptyMorphoEarnVaultDetailFields() {
|
|
182
|
+
return {
|
|
183
|
+
liquidityUsdLabel: "\u2014",
|
|
184
|
+
netApy7dLabel: "\u2014",
|
|
185
|
+
netApy30dLabel: "\u2014",
|
|
186
|
+
netApy90dLabel: "\u2014",
|
|
187
|
+
exposure: []
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
async function fetchV1EarnVaultLightBatch(args) {
|
|
191
|
+
const out = /* @__PURE__ */ new Map();
|
|
192
|
+
if (!args.addresses.length) return out;
|
|
193
|
+
const d = await morphoGql(
|
|
194
|
+
`
|
|
195
|
+
query MorphoV1EarnVaultLight($chainId: Int!, $addresses: [String!]!) {
|
|
196
|
+
vaults(where: { chainId_in: [$chainId], address_in: $addresses, listed: true }) {
|
|
197
|
+
items {
|
|
198
|
+
address
|
|
199
|
+
liquidity { usd }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
`,
|
|
204
|
+
{ chainId: args.chainId, addresses: args.addresses.map((a) => viem.getAddress(a)) }
|
|
205
|
+
);
|
|
206
|
+
for (const v of d.vaults?.items ?? []) {
|
|
207
|
+
const addr = (v.address ?? "").trim();
|
|
208
|
+
if (!viem.isAddress(addr)) continue;
|
|
209
|
+
out.set(viem.getAddress(addr).toLowerCase(), {
|
|
210
|
+
...emptyMorphoEarnVaultDetailFields(),
|
|
211
|
+
liquidityUsdLabel: formatMorphoUsd(v.liquidity?.usd)
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
async function fetchV1EarnVaultPeriodApys(args) {
|
|
217
|
+
const d = await morphoGql(
|
|
218
|
+
`
|
|
219
|
+
query MorphoV1EarnVaultPeriodApys($chainId: Int!, $address: String!) {
|
|
220
|
+
vaultByAddress(chainId: $chainId, address: $address) {
|
|
221
|
+
historicalState {
|
|
222
|
+
weeklyNetApy { y }
|
|
223
|
+
monthlyNetApy { y }
|
|
224
|
+
quarterlyNetApy { y }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
`,
|
|
229
|
+
{ chainId: args.chainId, address: viem.getAddress(args.address) }
|
|
230
|
+
);
|
|
231
|
+
const hs = d.vaultByAddress?.historicalState;
|
|
232
|
+
return {
|
|
233
|
+
netApy7dLabel: formatMorphoApyPct(latestHistoryApy(hs?.weeklyNetApy)),
|
|
234
|
+
netApy30dLabel: formatMorphoApyPct(latestHistoryApy(hs?.monthlyNetApy)),
|
|
235
|
+
netApy90dLabel: formatMorphoApyPct(latestHistoryApy(hs?.quarterlyNetApy))
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async function fetchV2EarnVaultLightBatch(args) {
|
|
239
|
+
const out = /* @__PURE__ */ new Map();
|
|
240
|
+
if (!args.addresses.length) return out;
|
|
241
|
+
const d = await morphoGql(
|
|
242
|
+
`
|
|
243
|
+
query MorphoV2EarnVaultLight($chainId: Int!, $addresses: [String!]!) {
|
|
244
|
+
vaultV2s(where: { chainId_in: [$chainId], address_in: $addresses, listed: true }) {
|
|
245
|
+
items {
|
|
246
|
+
address
|
|
247
|
+
liquidityUsd
|
|
248
|
+
avgNetApy7d: avgNetApy(lookback: SEVEN_DAYS)
|
|
249
|
+
avgNetApy30d: avgNetApy(lookback: THIRTY_DAYS)
|
|
250
|
+
avgNetApy90d: avgNetApy(lookback: NINETY_DAYS)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
`,
|
|
255
|
+
{
|
|
256
|
+
chainId: args.chainId,
|
|
257
|
+
addresses: args.addresses.map((a) => viem.getAddress(a))
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
for (const v of d.vaultV2s?.items ?? []) {
|
|
261
|
+
const addr = (v.address ?? "").trim();
|
|
262
|
+
if (!viem.isAddress(addr)) continue;
|
|
263
|
+
out.set(viem.getAddress(addr).toLowerCase(), {
|
|
264
|
+
liquidityUsdLabel: formatMorphoUsd(v.liquidityUsd),
|
|
265
|
+
netApy7dLabel: formatMorphoApyPct(v.avgNetApy7d),
|
|
266
|
+
netApy30dLabel: formatMorphoApyPct(v.avgNetApy30d),
|
|
267
|
+
netApy90dLabel: formatMorphoApyPct(v.avgNetApy90d),
|
|
268
|
+
exposure: []
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
async function fetchV1EarnVaultExposure(args) {
|
|
274
|
+
const d = await morphoGql(
|
|
275
|
+
`
|
|
276
|
+
query MorphoV1EarnVaultExposure($chainId: Int!, $address: String!) {
|
|
277
|
+
vaultByAddress(chainId: $chainId, address: $address) {
|
|
278
|
+
address
|
|
279
|
+
state {
|
|
280
|
+
totalAssetsUsd
|
|
281
|
+
allocation {
|
|
282
|
+
supplyAssetsUsd
|
|
283
|
+
market { loanAsset { symbol } collateralAsset { symbol } }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
`,
|
|
289
|
+
{ chainId: args.chainId, address: viem.getAddress(args.address) }
|
|
290
|
+
);
|
|
291
|
+
const v = d.vaultByAddress;
|
|
292
|
+
if (!v?.address) return [];
|
|
293
|
+
return parseV1Exposure(v.state?.allocation ?? [], v.state?.totalAssetsUsd ?? 0);
|
|
294
|
+
}
|
|
295
|
+
async function fetchV2EarnVaultExposure(args) {
|
|
296
|
+
const d = await morphoGql(
|
|
297
|
+
`
|
|
298
|
+
query MorphoV2EarnVaultExposure($chainId: Int!, $address: String!, $posLimit: Int!) {
|
|
299
|
+
vaultV2ByAddress(chainId: $chainId, address: $address) {
|
|
300
|
+
address
|
|
301
|
+
totalAssetsUsd
|
|
302
|
+
adapters {
|
|
303
|
+
items {
|
|
304
|
+
__typename
|
|
305
|
+
assetsUsd
|
|
306
|
+
... on MorphoVaultV2Adapter {
|
|
307
|
+
innerVault { name asset { symbol } }
|
|
308
|
+
}
|
|
309
|
+
... on MorphoMarketV1Adapter {
|
|
310
|
+
positions(first: $posLimit) {
|
|
311
|
+
items {
|
|
312
|
+
market { loanAsset { symbol } collateralAsset { symbol } }
|
|
313
|
+
state { supplyAssetsUsd }
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
`,
|
|
322
|
+
{ chainId: args.chainId, address: viem.getAddress(args.address), posLimit: V2_EXPOSURE_POSITION_LIMIT }
|
|
323
|
+
);
|
|
324
|
+
const v = d.vaultV2ByAddress;
|
|
325
|
+
if (!v?.address) return [];
|
|
326
|
+
return parseV2Exposure(v.adapters?.items ?? [], v.totalAssetsUsd ?? 0);
|
|
327
|
+
}
|
|
328
|
+
async function fetchMorphoEarnVaultDetails(args) {
|
|
329
|
+
const addr = args.vaultAddress.trim();
|
|
330
|
+
if (!viem.isAddress(addr)) return emptyMorphoEarnVaultDetailFields();
|
|
331
|
+
if (args.vaultGeneration === "v1") {
|
|
332
|
+
const [light2, periodApys, exposure2] = await Promise.all([
|
|
333
|
+
fetchV1EarnVaultLightBatch({ chainId: args.chainId, addresses: [addr] }),
|
|
334
|
+
fetchV1EarnVaultPeriodApys({ chainId: args.chainId, address: addr }),
|
|
335
|
+
fetchV1EarnVaultExposure({ chainId: args.chainId, address: addr })
|
|
336
|
+
]);
|
|
337
|
+
const detail2 = light2.get(viem.getAddress(addr).toLowerCase()) ?? emptyMorphoEarnVaultDetailFields();
|
|
338
|
+
return { ...detail2, ...periodApys, exposure: exposure2 };
|
|
339
|
+
}
|
|
340
|
+
const [light, exposure] = await Promise.all([
|
|
341
|
+
fetchV2EarnVaultLightBatch({ chainId: args.chainId, addresses: [addr] }),
|
|
342
|
+
fetchV2EarnVaultExposure({ chainId: args.chainId, address: addr })
|
|
343
|
+
]);
|
|
344
|
+
const detail = light.get(viem.getAddress(addr).toLowerCase()) ?? emptyMorphoEarnVaultDetailFields();
|
|
345
|
+
return { ...detail, exposure };
|
|
346
|
+
}
|
|
347
|
+
async function enrichMorphoEarnOfferingRowsLight(chainId, rows) {
|
|
348
|
+
if (!rows.length) return rows;
|
|
349
|
+
const v1Addrs = [];
|
|
350
|
+
const v2Addrs = [];
|
|
351
|
+
for (const r of rows) {
|
|
352
|
+
if (r.vaultGeneration === "v1") v1Addrs.push(r.vaultAddress);
|
|
353
|
+
else v2Addrs.push(r.vaultAddress);
|
|
354
|
+
}
|
|
355
|
+
const detailByAddr = /* @__PURE__ */ new Map();
|
|
356
|
+
for (let i = 0; i < v1Addrs.length; i += LIGHT_BATCH_SIZE) {
|
|
357
|
+
const batch = await fetchV1EarnVaultLightBatch({ chainId, addresses: v1Addrs.slice(i, i + LIGHT_BATCH_SIZE) });
|
|
358
|
+
for (const [k, v] of batch) detailByAddr.set(k, v);
|
|
359
|
+
}
|
|
360
|
+
for (let i = 0; i < v2Addrs.length; i += LIGHT_BATCH_SIZE) {
|
|
361
|
+
const batch = await fetchV2EarnVaultLightBatch({ chainId, addresses: v2Addrs.slice(i, i + LIGHT_BATCH_SIZE) });
|
|
362
|
+
for (const [k, v] of batch) detailByAddr.set(k, v);
|
|
363
|
+
}
|
|
364
|
+
return rows.map((row) => {
|
|
365
|
+
const detail = detailByAddr.get(row.vaultAddress.toLowerCase());
|
|
366
|
+
if (!detail) return row;
|
|
367
|
+
return {
|
|
368
|
+
...row,
|
|
369
|
+
liquidityUsdLabel: detail.liquidityUsdLabel !== "\u2014" ? detail.liquidityUsdLabel : row.liquidityUsdLabel,
|
|
370
|
+
netApy7dLabel: detail.netApy7dLabel,
|
|
371
|
+
netApy30dLabel: detail.netApy30dLabel,
|
|
372
|
+
netApy90dLabel: detail.netApy90dLabel
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
async function attachAgentVaultDetails(chainId, rows) {
|
|
377
|
+
if (!rows.length) return rows;
|
|
378
|
+
const out = [...rows];
|
|
379
|
+
for (let i = 0; i < out.length; i += EXPOSURE_FETCH_CONCURRENCY) {
|
|
380
|
+
const chunk = out.slice(i, i + EXPOSURE_FETCH_CONCURRENCY);
|
|
381
|
+
const details = await Promise.all(
|
|
382
|
+
chunk.map(async (row) => {
|
|
383
|
+
const exposure = row.vaultGeneration === "v1" ? await fetchV1EarnVaultExposure({ chainId, address: row.vaultAddress }) : await fetchV2EarnVaultExposure({ chainId, address: row.vaultAddress });
|
|
384
|
+
const periodApys = row.vaultGeneration === "v1" ? await fetchV1EarnVaultPeriodApys({ chainId, address: row.vaultAddress }) : {
|
|
385
|
+
netApy7dLabel: row.netApy7dLabel,
|
|
386
|
+
netApy30dLabel: row.netApy30dLabel,
|
|
387
|
+
netApy90dLabel: row.netApy90dLabel
|
|
388
|
+
};
|
|
389
|
+
return { exposure, periodApys };
|
|
390
|
+
})
|
|
391
|
+
);
|
|
392
|
+
for (let j = 0; j < chunk.length; j++) {
|
|
393
|
+
out[i + j] = { ...out[i + j], ...details[j].periodApys, exposure: details[j].exposure };
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
398
|
+
async function enrichMorphoEarnOfferingRows(chainId, rows) {
|
|
399
|
+
return enrichMorphoEarnOfferingRowsLight(chainId, rows);
|
|
400
|
+
}
|
|
401
|
+
async function enrichMorphoEarnOfferingRowsForAgent(chainId, rows) {
|
|
402
|
+
const light = await enrichMorphoEarnOfferingRowsLight(chainId, rows);
|
|
403
|
+
return attachAgentVaultDetails(chainId, light);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/protocols/evm/morpho/vaultMarkets.ts
|
|
407
|
+
var EMPTY_EARN_DETAIL = emptyMorphoEarnVaultDetailFields();
|
|
408
|
+
var VAULT_PAGE_SIZE = 100;
|
|
409
|
+
var VAULT_ASSET_QUERY_LIMIT = 200;
|
|
410
|
+
var DISCOVERY_DEFAULT_LIMIT = 50;
|
|
411
|
+
var DISCOVERY_CHAIN_FETCH_LIMIT = 300;
|
|
412
|
+
function parseUsdLabel(s) {
|
|
413
|
+
const n = parseFloat(s.replace(/[^0-9.]/g, ""));
|
|
414
|
+
return Number.isFinite(n) ? n : 0;
|
|
415
|
+
}
|
|
416
|
+
function sortEarnRowsByTvl(rows) {
|
|
417
|
+
return [...rows].sort((a, b) => parseUsdLabel(b.totalAssetsUsdLabel) - parseUsdLabel(a.totalAssetsUsdLabel));
|
|
418
|
+
}
|
|
419
|
+
function morphoEarnOfferingToDiscoveryRow(row) {
|
|
420
|
+
const mgmt = row.managementFeeLabel;
|
|
421
|
+
return {
|
|
422
|
+
vaultAddress: row.vaultAddress,
|
|
423
|
+
vaultName: row.vaultName,
|
|
424
|
+
vaultSymbol: row.vaultSymbol,
|
|
425
|
+
underlyingAddress: row.underlyingAddress,
|
|
426
|
+
underlyingSymbol: row.underlyingSymbol,
|
|
427
|
+
apy: row.apyLabel,
|
|
428
|
+
netApy: row.netApyLabel,
|
|
429
|
+
netApy7d: row.netApy7dLabel,
|
|
430
|
+
netApy30d: row.netApy30dLabel,
|
|
431
|
+
netApy90d: row.netApy90dLabel,
|
|
432
|
+
performanceFee: row.performanceFeeLabel,
|
|
433
|
+
managementFee: mgmt === "\u2014" || mgmt === "0.00%" ? null : mgmt,
|
|
434
|
+
totalDepositsUsd: row.totalAssetsUsdLabel,
|
|
435
|
+
liquidityUsd: row.liquidityUsdLabel,
|
|
436
|
+
exposure: row.exposure
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function matchesMorphoVaultSearch(row, query) {
|
|
440
|
+
const q = query.trim().toLowerCase();
|
|
441
|
+
if (!q) return true;
|
|
442
|
+
const haystack = [row.vaultName, row.vaultSymbol, row.vaultAddress, row.underlyingSymbol].join(" ").toLowerCase();
|
|
443
|
+
return haystack.includes(q);
|
|
444
|
+
}
|
|
445
|
+
function matchesMorphoUnderlyingFilter(row, underlying) {
|
|
446
|
+
const u = underlying.trim();
|
|
447
|
+
if (!u) return true;
|
|
448
|
+
if (viem.isAddress(u)) {
|
|
449
|
+
try {
|
|
450
|
+
return row.underlyingAddress.toLowerCase() === viem.getAddress(u).toLowerCase();
|
|
451
|
+
} catch {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return row.underlyingSymbol.toLowerCase().includes(u.toLowerCase());
|
|
456
|
+
}
|
|
457
|
+
async function fetchAllListedEarnVaultsForChain(chainId) {
|
|
458
|
+
const [v1Vaults, v2Vaults] = await Promise.all([
|
|
459
|
+
fetchMorphoVaultsForChain(chainId, DISCOVERY_CHAIN_FETCH_LIMIT),
|
|
460
|
+
fetchMorphoVaultV2sForChain(chainId, DISCOVERY_CHAIN_FETCH_LIMIT, 0)
|
|
461
|
+
]);
|
|
462
|
+
const byAddr = /* @__PURE__ */ new Map();
|
|
463
|
+
const defaultDecimals = 18;
|
|
464
|
+
for (const v of v1Vaults) {
|
|
465
|
+
if (v.listed === false) continue;
|
|
466
|
+
const row = earnRowFromVault({
|
|
467
|
+
chainId,
|
|
468
|
+
address: v.address,
|
|
469
|
+
symbol: v.symbol,
|
|
470
|
+
name: v.name,
|
|
471
|
+
asset: v.asset,
|
|
472
|
+
apy: v.state?.apy,
|
|
473
|
+
netApy: v.state?.netApy,
|
|
474
|
+
performanceFee: v.state?.fee,
|
|
475
|
+
managementFee: null,
|
|
476
|
+
totalAssetsUsd: v.state?.totalAssetsUsd,
|
|
477
|
+
underlyingDecimals: v.asset?.decimals ?? defaultDecimals,
|
|
478
|
+
vaultGeneration: "v1"
|
|
479
|
+
});
|
|
480
|
+
if (row) byAddr.set(row.vaultAddress.toLowerCase(), row);
|
|
481
|
+
}
|
|
482
|
+
for (const v of v2Vaults) {
|
|
483
|
+
if (v.listed === false) continue;
|
|
484
|
+
const row = earnRowFromVault({
|
|
485
|
+
chainId,
|
|
486
|
+
address: v.address,
|
|
487
|
+
symbol: v.symbol,
|
|
488
|
+
name: v.name,
|
|
489
|
+
asset: v.asset,
|
|
490
|
+
apy: v.apy,
|
|
491
|
+
netApy: v.netApy,
|
|
492
|
+
performanceFee: v.performanceFee,
|
|
493
|
+
managementFee: v.managementFee,
|
|
494
|
+
totalAssetsUsd: v.totalAssetsUsd,
|
|
495
|
+
underlyingDecimals: v.asset?.decimals ?? defaultDecimals,
|
|
496
|
+
vaultGeneration: "v2"
|
|
497
|
+
});
|
|
498
|
+
if (row) byAddr.set(row.vaultAddress.toLowerCase(), row);
|
|
499
|
+
}
|
|
500
|
+
return sortEarnRowsByTvl([...byAddr.values()]);
|
|
501
|
+
}
|
|
502
|
+
async function searchMorphoListedEarnVaults(args) {
|
|
503
|
+
const limit = args.limit ?? DISCOVERY_DEFAULT_LIMIT;
|
|
504
|
+
const underlying = args.underlying?.trim() ?? "";
|
|
505
|
+
const query = args.query?.trim() ?? "";
|
|
506
|
+
let rows;
|
|
507
|
+
const enrichedFromAsset = Boolean(underlying && viem.isAddress(underlying));
|
|
508
|
+
if (enrichedFromAsset) {
|
|
509
|
+
rows = await fetchMorphoEarnOfferingsForAsset({
|
|
510
|
+
chainId: args.chainId,
|
|
511
|
+
contractAddress: underlying,
|
|
512
|
+
underlyingDecimals: 18,
|
|
513
|
+
suppliedAssetSymbol: void 0
|
|
514
|
+
});
|
|
515
|
+
} else {
|
|
516
|
+
rows = await fetchAllListedEarnVaultsForChain(args.chainId);
|
|
517
|
+
}
|
|
518
|
+
if (underlying) {
|
|
519
|
+
rows = rows.filter((r) => matchesMorphoUnderlyingFilter(morphoEarnOfferingToDiscoveryRow(r), underlying));
|
|
520
|
+
}
|
|
521
|
+
if (query) {
|
|
522
|
+
rows = rows.filter((r) => matchesMorphoVaultSearch(morphoEarnOfferingToDiscoveryRow(r), query));
|
|
523
|
+
}
|
|
524
|
+
rows = rows.slice(0, Math.max(1, Math.min(limit, 200)));
|
|
525
|
+
rows = await enrichMorphoEarnOfferingRowsForAgent(args.chainId, rows);
|
|
526
|
+
return rows.map(morphoEarnOfferingToDiscoveryRow);
|
|
527
|
+
}
|
|
528
|
+
async function isMorphoVaultListed(args) {
|
|
529
|
+
const row = await fetchMorphoVaultByAddress(args);
|
|
530
|
+
return row?.listed === true;
|
|
531
|
+
}
|
|
532
|
+
function earnRowFromVault(args) {
|
|
533
|
+
const assetAddr = (args.asset?.address ?? "").trim();
|
|
534
|
+
if (!viem.isAddress(assetAddr)) return null;
|
|
535
|
+
const vaultAddress = viem.getAddress(args.address);
|
|
536
|
+
const sym = (args.symbol ?? args.name ?? "Vault").trim() || "Vault";
|
|
537
|
+
const vaultName = (args.name ?? sym).trim() || sym;
|
|
538
|
+
const underlyingSymbol = (args.asset?.symbol ?? args.suppliedAssetSymbol ?? "ASSET").trim();
|
|
539
|
+
const dec = args.asset?.decimals ?? args.underlyingDecimals;
|
|
540
|
+
return {
|
|
541
|
+
vaultAddress,
|
|
542
|
+
vaultSymbol: sym,
|
|
543
|
+
vaultName,
|
|
544
|
+
underlyingAddress: viem.getAddress(assetAddr),
|
|
545
|
+
underlyingSymbol,
|
|
546
|
+
underlyingDecimals: dec,
|
|
547
|
+
apyLabel: formatMorphoApyPct(args.apy),
|
|
548
|
+
netApyLabel: formatMorphoApyPct(args.netApy),
|
|
549
|
+
performanceFeeLabel: formatMorphoFeePct(args.performanceFee),
|
|
550
|
+
managementFeeLabel: formatMorphoFeePct(args.managementFee),
|
|
551
|
+
totalAssetsUsdLabel: formatMorphoUsd(args.totalAssetsUsd),
|
|
552
|
+
liquidityUsdLabel: formatMorphoUsd(args.liquidityUsd),
|
|
553
|
+
netApy7dLabel: EMPTY_EARN_DETAIL.netApy7dLabel,
|
|
554
|
+
netApy30dLabel: EMPTY_EARN_DETAIL.netApy30dLabel,
|
|
555
|
+
netApy90dLabel: EMPTY_EARN_DETAIL.netApy90dLabel,
|
|
556
|
+
exposure: EMPTY_EARN_DETAIL.exposure,
|
|
557
|
+
vaultId: `${args.chainId}:${vaultAddress.toLowerCase()}`,
|
|
558
|
+
vaultGeneration: args.vaultGeneration
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
async function fetchMorphoVaultsForChain(chainId, first = 100) {
|
|
562
|
+
const d = await morphoGql(
|
|
563
|
+
`
|
|
564
|
+
query MorphoVaults($chainId: Int!, $first: Int!) {
|
|
565
|
+
vaults(first: $first, where: { chainId_in: [$chainId], listed: true }, orderBy: TotalAssetsUsd, orderDirection: Desc) {
|
|
566
|
+
items {
|
|
567
|
+
address symbol name listed
|
|
568
|
+
asset { address symbol decimals price { usd } }
|
|
569
|
+
chain { id }
|
|
570
|
+
state { apy netApy fee totalAssetsUsd allRewards { supplyApr asset { address symbol } } }
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
`,
|
|
575
|
+
{ chainId, first }
|
|
576
|
+
);
|
|
577
|
+
return d.vaults?.items ?? [];
|
|
578
|
+
}
|
|
579
|
+
async function fetchMorphoVaultV2sForChain(chainId, first = 100, skip = 0) {
|
|
580
|
+
const d = await morphoGql(
|
|
581
|
+
`
|
|
582
|
+
query MorphoVaultV2s($chainId: Int!, $first: Int!, $skip: Int!) {
|
|
583
|
+
vaultV2s(
|
|
584
|
+
first: $first
|
|
585
|
+
skip: $skip
|
|
586
|
+
where: { chainId_in: [$chainId], listed: true }
|
|
587
|
+
orderBy: TotalAssetsUsd
|
|
588
|
+
orderDirection: Desc
|
|
589
|
+
) {
|
|
590
|
+
items {
|
|
591
|
+
address symbol name listed
|
|
592
|
+
asset { address symbol decimals price { usd } }
|
|
593
|
+
chain { id }
|
|
594
|
+
apy netApy performanceFee managementFee totalAssetsUsd
|
|
595
|
+
rewards { supplyApr asset { address symbol } }
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
`,
|
|
600
|
+
{ chainId, first, skip }
|
|
601
|
+
);
|
|
602
|
+
return d.vaultV2s?.items ?? [];
|
|
603
|
+
}
|
|
604
|
+
async function fetchMorphoVaultsForAssetAddress(args) {
|
|
605
|
+
const asset = viem.getAddress(args.assetAddress);
|
|
606
|
+
const d = await morphoGql(
|
|
607
|
+
`
|
|
608
|
+
query MorphoVaultsForAsset($chainId: Int!, $asset: String!, $first: Int!) {
|
|
609
|
+
vaults(
|
|
610
|
+
first: $first
|
|
611
|
+
where: { chainId_in: [$chainId], assetAddress_in: [$asset], listed: true }
|
|
612
|
+
orderBy: TotalAssetsUsd
|
|
613
|
+
orderDirection: Desc
|
|
614
|
+
) {
|
|
615
|
+
items {
|
|
616
|
+
address symbol name listed
|
|
617
|
+
asset { address symbol decimals }
|
|
618
|
+
liquidity { usd }
|
|
619
|
+
state { apy netApy fee totalAssetsUsd }
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
`,
|
|
624
|
+
{ chainId: args.chainId, asset, first: VAULT_ASSET_QUERY_LIMIT }
|
|
625
|
+
);
|
|
626
|
+
return d.vaults?.items ?? [];
|
|
627
|
+
}
|
|
628
|
+
async function fetchMorphoVaultV2sForAssetAddress(args) {
|
|
629
|
+
const asset = viem.getAddress(args.assetAddress);
|
|
630
|
+
const d = await morphoGql(
|
|
631
|
+
`
|
|
632
|
+
query MorphoVaultV2sForAsset($chainId: Int!, $asset: Address!, $first: Int!) {
|
|
633
|
+
vaultV2s(
|
|
634
|
+
first: $first
|
|
635
|
+
where: { chainId_in: [$chainId], assetAddress_in: [$asset], listed: true }
|
|
636
|
+
orderBy: TotalAssetsUsd
|
|
637
|
+
orderDirection: Desc
|
|
638
|
+
) {
|
|
639
|
+
items {
|
|
640
|
+
address symbol name listed
|
|
641
|
+
asset { address symbol decimals }
|
|
642
|
+
apy netApy performanceFee managementFee totalAssetsUsd liquidityUsd
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
`,
|
|
647
|
+
{ chainId: args.chainId, asset, first: VAULT_ASSET_QUERY_LIMIT }
|
|
648
|
+
);
|
|
649
|
+
return d.vaultV2s?.items ?? [];
|
|
650
|
+
}
|
|
651
|
+
async function fetchAllMorphoVaultV2AssetAddresses(chainId) {
|
|
652
|
+
const out = /* @__PURE__ */ new Set();
|
|
653
|
+
let skip = 0;
|
|
654
|
+
for (let page = 0; page < 20; page++) {
|
|
655
|
+
const batch = await fetchMorphoVaultV2sForChain(chainId, VAULT_PAGE_SIZE, skip);
|
|
656
|
+
if (!batch.length) break;
|
|
657
|
+
for (const v of batch) {
|
|
658
|
+
const a = (v.asset?.address ?? "").trim();
|
|
659
|
+
if (viem.isAddress(a)) out.add(viem.getAddress(a).toLowerCase());
|
|
660
|
+
}
|
|
661
|
+
if (batch.length < VAULT_PAGE_SIZE) break;
|
|
662
|
+
skip += VAULT_PAGE_SIZE;
|
|
663
|
+
}
|
|
664
|
+
return out;
|
|
665
|
+
}
|
|
666
|
+
async function fetchMorphoVaultByAddress(args) {
|
|
667
|
+
const addr = args.vaultAddress.trim();
|
|
668
|
+
if (!viem.isAddress(addr)) return null;
|
|
669
|
+
const checksummed = viem.getAddress(addr);
|
|
670
|
+
const v2 = await morphoGql(
|
|
671
|
+
`
|
|
672
|
+
query MorphoVaultV2ByAddress($address: String!, $chainId: Int!) {
|
|
673
|
+
vaultV2ByAddress(address: $address, chainId: $chainId) {
|
|
674
|
+
address symbol name listed
|
|
675
|
+
asset { address symbol decimals price { usd } }
|
|
676
|
+
chain { id }
|
|
677
|
+
apy netApy totalAssetsUsd
|
|
678
|
+
rewards { supplyApr asset { address symbol } }
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
`,
|
|
682
|
+
{ address: checksummed, chainId: args.chainId }
|
|
683
|
+
).catch(() => ({ vaultV2ByAddress: null }));
|
|
684
|
+
if (v2.vaultV2ByAddress?.address) return v2.vaultV2ByAddress;
|
|
685
|
+
const d = await morphoGql(
|
|
686
|
+
`
|
|
687
|
+
query MorphoVaultByAddress($address: String!, $chainId: Int!) {
|
|
688
|
+
vaultByAddress(address: $address, chainId: $chainId) {
|
|
689
|
+
address symbol name listed
|
|
690
|
+
asset { address symbol decimals price { usd } }
|
|
691
|
+
chain { id }
|
|
692
|
+
state { apy netApy fee totalAssetsUsd allRewards { supplyApr asset { address symbol } } }
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
`,
|
|
696
|
+
{ address: checksummed, chainId: args.chainId }
|
|
697
|
+
).catch(() => ({ vaultByAddress: null }));
|
|
698
|
+
return d.vaultByAddress;
|
|
699
|
+
}
|
|
700
|
+
async function fetchMorphoEarnOfferingsForAsset(args) {
|
|
701
|
+
const key = morphoKeyForAssetRow({ contractAddress: args.contractAddress });
|
|
702
|
+
if (!key) return [];
|
|
703
|
+
const assetAddress = viem.getAddress(key);
|
|
704
|
+
const [v1Vaults, v2Vaults] = await Promise.all([
|
|
705
|
+
fetchMorphoVaultsForAssetAddress({ chainId: args.chainId, assetAddress }),
|
|
706
|
+
fetchMorphoVaultV2sForAssetAddress({ chainId: args.chainId, assetAddress })
|
|
707
|
+
]);
|
|
708
|
+
const byAddr = /* @__PURE__ */ new Map();
|
|
709
|
+
for (const v of v1Vaults) {
|
|
710
|
+
const row = earnRowFromVault({
|
|
711
|
+
chainId: args.chainId,
|
|
712
|
+
address: v.address,
|
|
713
|
+
symbol: v.symbol,
|
|
714
|
+
name: v.name,
|
|
715
|
+
asset: v.asset,
|
|
716
|
+
apy: v.state?.apy,
|
|
717
|
+
netApy: v.state?.netApy,
|
|
718
|
+
performanceFee: v.state?.fee,
|
|
719
|
+
managementFee: null,
|
|
720
|
+
totalAssetsUsd: v.state?.totalAssetsUsd,
|
|
721
|
+
liquidityUsd: v.liquidity?.usd,
|
|
722
|
+
underlyingDecimals: args.underlyingDecimals,
|
|
723
|
+
suppliedAssetSymbol: args.suppliedAssetSymbol,
|
|
724
|
+
vaultGeneration: "v1"
|
|
725
|
+
});
|
|
726
|
+
if (row) byAddr.set(row.vaultAddress.toLowerCase(), row);
|
|
727
|
+
}
|
|
728
|
+
for (const v of v2Vaults) {
|
|
729
|
+
const row = earnRowFromVault({
|
|
730
|
+
chainId: args.chainId,
|
|
731
|
+
address: v.address,
|
|
732
|
+
symbol: v.symbol,
|
|
733
|
+
name: v.name,
|
|
734
|
+
asset: v.asset,
|
|
735
|
+
apy: v.apy,
|
|
736
|
+
netApy: v.netApy,
|
|
737
|
+
performanceFee: v.performanceFee,
|
|
738
|
+
managementFee: v.managementFee,
|
|
739
|
+
totalAssetsUsd: v.totalAssetsUsd,
|
|
740
|
+
liquidityUsd: v.liquidityUsd,
|
|
741
|
+
underlyingDecimals: args.underlyingDecimals,
|
|
742
|
+
suppliedAssetSymbol: args.suppliedAssetSymbol,
|
|
743
|
+
vaultGeneration: "v2"
|
|
744
|
+
});
|
|
745
|
+
if (row) byAddr.set(row.vaultAddress.toLowerCase(), row);
|
|
746
|
+
}
|
|
747
|
+
return sortEarnRowsByTvl([...byAddr.values()]);
|
|
748
|
+
}
|
|
749
|
+
var chainAssetCache = /* @__PURE__ */ new Map();
|
|
750
|
+
async function ensureMorphoChainAssetCache(chainId) {
|
|
751
|
+
const hit = chainAssetCache.get(chainId);
|
|
752
|
+
if (hit) return hit;
|
|
753
|
+
const modes = /* @__PURE__ */ new Map();
|
|
754
|
+
const [v1Vaults, v2Assets] = await Promise.all([
|
|
755
|
+
fetchMorphoVaultsForChain(chainId, 300),
|
|
756
|
+
fetchAllMorphoVaultV2AssetAddresses(chainId)
|
|
757
|
+
]);
|
|
758
|
+
for (const v of v1Vaults) {
|
|
759
|
+
const a = (v.asset?.address ?? "").trim();
|
|
760
|
+
if (!viem.isAddress(a)) continue;
|
|
761
|
+
const k = viem.getAddress(a).toLowerCase();
|
|
762
|
+
const prev = modes.get(k) ?? { earn: false, borrow: false, collateral: false };
|
|
763
|
+
prev.earn = true;
|
|
764
|
+
modes.set(k, prev);
|
|
765
|
+
}
|
|
766
|
+
for (const k of v2Assets) {
|
|
767
|
+
const prev = modes.get(k) ?? { earn: false, borrow: false, collateral: false };
|
|
768
|
+
prev.earn = true;
|
|
769
|
+
modes.set(k, prev);
|
|
770
|
+
}
|
|
771
|
+
const marketsD = await morphoGql(
|
|
772
|
+
`
|
|
773
|
+
query MorphoMarketsAssets($chainId: Int!) {
|
|
774
|
+
markets(first: 500, where: { chainId_in: [$chainId] }) {
|
|
775
|
+
items {
|
|
776
|
+
loanAsset { address }
|
|
777
|
+
collateralAsset { address }
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
`,
|
|
782
|
+
{ chainId }
|
|
783
|
+
);
|
|
784
|
+
for (const m of marketsD.markets?.items ?? []) {
|
|
785
|
+
const loanAddr = (m.loanAsset?.address ?? "").trim();
|
|
786
|
+
const colAddr = (m.collateralAsset?.address ?? "").trim();
|
|
787
|
+
if (viem.isAddress(loanAddr)) {
|
|
788
|
+
const k = viem.getAddress(loanAddr).toLowerCase();
|
|
789
|
+
const prev = modes.get(k) ?? { earn: false, borrow: false, collateral: false };
|
|
790
|
+
prev.borrow = true;
|
|
791
|
+
modes.set(k, prev);
|
|
792
|
+
}
|
|
793
|
+
if (viem.isAddress(colAddr)) {
|
|
794
|
+
const k = viem.getAddress(colAddr).toLowerCase();
|
|
795
|
+
const prev = modes.get(k) ?? { earn: false, borrow: false, collateral: false };
|
|
796
|
+
prev.collateral = true;
|
|
797
|
+
modes.set(k, prev);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
const cache = { modesByUnderlying: modes, nativeWrapped: null };
|
|
801
|
+
chainAssetCache.set(chainId, cache);
|
|
802
|
+
return cache;
|
|
803
|
+
}
|
|
804
|
+
async function morphoFetchEarnVaultsSummary(args) {
|
|
805
|
+
const vaults = await searchMorphoListedEarnVaults({
|
|
806
|
+
chainId: args.chainId,
|
|
807
|
+
underlying: args.underlying?.trim() || void 0,
|
|
808
|
+
query: args.query?.trim() || void 0,
|
|
809
|
+
limit: args.limit
|
|
810
|
+
});
|
|
811
|
+
return { vaults };
|
|
812
|
+
}
|
|
813
|
+
async function morphoResolveListedEarnVaultByAddress(args) {
|
|
814
|
+
const addr = args.vaultAddress.trim();
|
|
815
|
+
if (!viem.isAddress(addr)) return null;
|
|
816
|
+
const rows = await searchMorphoListedEarnVaults({
|
|
817
|
+
chainId: args.chainId,
|
|
818
|
+
query: viem.getAddress(addr),
|
|
819
|
+
limit: 5
|
|
820
|
+
});
|
|
821
|
+
const hit = rows.find((r) => r.vaultAddress.toLowerCase() === viem.getAddress(addr).toLowerCase());
|
|
822
|
+
return hit ?? null;
|
|
823
|
+
}
|
|
824
|
+
function lltvToPctLabel(lltv) {
|
|
825
|
+
try {
|
|
826
|
+
const wad = BigInt(lltv);
|
|
827
|
+
const pct = Number(wad) / 1e16;
|
|
828
|
+
if (!Number.isFinite(pct)) return "\u2014";
|
|
829
|
+
return `${pct.toFixed(1)}%`;
|
|
830
|
+
} catch {
|
|
831
|
+
return "\u2014";
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
async function fetchMorphoMarketsForChain(chainId, first = 200) {
|
|
835
|
+
const d = await morphoGql(
|
|
836
|
+
`
|
|
837
|
+
query MorphoMarkets($chainId: Int!, $first: Int!) {
|
|
838
|
+
markets(first: $first, where: { chainId_in: [$chainId] }, orderBy: BorrowAssetsUsd, orderDirection: Desc) {
|
|
839
|
+
items {
|
|
840
|
+
marketId
|
|
841
|
+
loanAsset { address symbol decimals price { usd } }
|
|
842
|
+
collateralAsset { address symbol decimals price { usd } }
|
|
843
|
+
lltv
|
|
844
|
+
oracle { address }
|
|
845
|
+
irmAddress
|
|
846
|
+
morphoBlue { address }
|
|
847
|
+
state {
|
|
848
|
+
supplyApy borrowApy netSupplyApy netBorrowApy
|
|
849
|
+
supplyAssetsUsd borrowAssetsUsd collateralAssetsUsd utilization
|
|
850
|
+
rewards { supplyApr borrowApr asset { address symbol } }
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
`,
|
|
856
|
+
{ chainId, first }
|
|
857
|
+
);
|
|
858
|
+
return d.markets?.items ?? [];
|
|
859
|
+
}
|
|
860
|
+
function morphoMarketToBorrowRow(m) {
|
|
861
|
+
if (!m.marketId || !m.morphoBlue?.address) return null;
|
|
862
|
+
const loan = m.loanAsset;
|
|
863
|
+
const col = m.collateralAsset;
|
|
864
|
+
if (!viem.isAddress(loan?.address ?? "") || !viem.isAddress(col?.address ?? "")) return null;
|
|
865
|
+
const loanSym = (loan.symbol ?? "LOAN").trim();
|
|
866
|
+
const colSym = (col.symbol ?? "COL").trim();
|
|
867
|
+
return {
|
|
868
|
+
marketId: m.marketId,
|
|
869
|
+
morphoBlueAddress: viem.getAddress(m.morphoBlue.address),
|
|
870
|
+
loanAssetAddress: viem.getAddress(loan.address),
|
|
871
|
+
loanAssetSymbol: loanSym,
|
|
872
|
+
loanAssetDecimals: loan.decimals ?? 18,
|
|
873
|
+
collateralAssetAddress: viem.getAddress(col.address),
|
|
874
|
+
collateralAssetSymbol: colSym,
|
|
875
|
+
collateralAssetDecimals: col.decimals ?? 18,
|
|
876
|
+
borrowApyLabel: formatMorphoApyPct(m.state?.borrowApy),
|
|
877
|
+
supplyApyLabel: formatMorphoApyPct(m.state?.supplyApy),
|
|
878
|
+
borrowAssetsUsdLabel: formatMorphoUsd(m.state?.borrowAssetsUsd),
|
|
879
|
+
lltvLabel: lltvToPctLabel(m.lltv),
|
|
880
|
+
marketLabel: `${colSym}/${loanSym}`
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
async function fetchMorphoBorrowMarketsForCollateral(args) {
|
|
884
|
+
const key = morphoKeyForAssetRow({ contractAddress: args.collateralAddress });
|
|
885
|
+
if (!key) return [];
|
|
886
|
+
const markets = await fetchMorphoMarketsForChain(args.chainId, 300);
|
|
887
|
+
const out = [];
|
|
888
|
+
for (const m of markets) {
|
|
889
|
+
const col = (m.collateralAsset?.address ?? "").trim().toLowerCase();
|
|
890
|
+
if (!col || col !== key) continue;
|
|
891
|
+
const row = morphoMarketToBorrowRow(m);
|
|
892
|
+
if (row) out.push(row);
|
|
893
|
+
}
|
|
894
|
+
return out;
|
|
895
|
+
}
|
|
896
|
+
async function fetchMorphoBorrowMarketsForLoan(args) {
|
|
897
|
+
const key = morphoKeyForAssetRow({ contractAddress: args.loanAddress });
|
|
898
|
+
if (!key) return [];
|
|
899
|
+
const markets = await fetchMorphoMarketsForChain(args.chainId, 300);
|
|
900
|
+
const out = [];
|
|
901
|
+
for (const m of markets) {
|
|
902
|
+
const loan = (m.loanAsset?.address ?? "").trim().toLowerCase();
|
|
903
|
+
if (!loan || loan !== key) continue;
|
|
904
|
+
const row = morphoMarketToBorrowRow(m);
|
|
905
|
+
if (row) out.push(row);
|
|
906
|
+
}
|
|
907
|
+
return out;
|
|
908
|
+
}
|
|
909
|
+
function formatMorphoPositionAssets(raw, decimals) {
|
|
910
|
+
if (raw == null) return "0";
|
|
911
|
+
const s = String(raw).trim();
|
|
912
|
+
if (!s || s === "0") return "0";
|
|
913
|
+
try {
|
|
914
|
+
return viem.formatUnits(BigInt(s), decimals);
|
|
915
|
+
} catch {
|
|
916
|
+
return s;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function isZeroPositionAmount(raw) {
|
|
920
|
+
if (raw == null) return true;
|
|
921
|
+
const s = String(raw).trim();
|
|
922
|
+
return !s || s === "0" || s === "0.0";
|
|
923
|
+
}
|
|
924
|
+
async function fetchMorphoUserVaultPositions(args) {
|
|
925
|
+
const user = args.user.trim();
|
|
926
|
+
if (!viem.isAddress(user)) return [];
|
|
927
|
+
const d = await morphoGql(
|
|
928
|
+
`
|
|
929
|
+
query MorphoUserVaultPositions($address: String!, $chainId: Int!) {
|
|
930
|
+
userByAddress(address: $address, chainId: $chainId) {
|
|
931
|
+
vaultPositions {
|
|
932
|
+
vault { address symbol asset { symbol decimals } }
|
|
933
|
+
state { assets assetsUsd shares }
|
|
934
|
+
}
|
|
935
|
+
vaultV2Positions {
|
|
936
|
+
vault { address symbol asset { symbol decimals } }
|
|
937
|
+
assets assetsUsd shares
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
`,
|
|
942
|
+
{ address: viem.getAddress(user), chainId: args.chainId }
|
|
943
|
+
);
|
|
944
|
+
const byVault = /* @__PURE__ */ new Map();
|
|
945
|
+
for (const p of d.userByAddress?.vaultPositions ?? []) {
|
|
946
|
+
const vaultAddr = (p.vault?.address ?? "").trim();
|
|
947
|
+
if (!viem.isAddress(vaultAddr)) continue;
|
|
948
|
+
const rawAssets = p.state?.assets;
|
|
949
|
+
if (isZeroPositionAmount(rawAssets)) continue;
|
|
950
|
+
const decimals = p.vault?.asset?.decimals ?? 18;
|
|
951
|
+
const vaultAddress = viem.getAddress(vaultAddr);
|
|
952
|
+
byVault.set(vaultAddress.toLowerCase(), {
|
|
953
|
+
vaultAddress,
|
|
954
|
+
vaultSymbol: (p.vault?.symbol ?? "Vault").trim(),
|
|
955
|
+
underlyingSymbol: (p.vault?.asset?.symbol ?? "").trim() || "\u2014",
|
|
956
|
+
assetsHuman: formatMorphoPositionAssets(rawAssets, decimals),
|
|
957
|
+
assetsUsdLabel: formatMorphoUsd(p.state?.assetsUsd),
|
|
958
|
+
shares: String(p.state?.shares ?? "0").trim()
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
for (const p of d.userByAddress?.vaultV2Positions ?? []) {
|
|
962
|
+
const vaultAddr = (p.vault?.address ?? "").trim();
|
|
963
|
+
if (!viem.isAddress(vaultAddr)) continue;
|
|
964
|
+
const rawAssets = p.assets;
|
|
965
|
+
if (isZeroPositionAmount(rawAssets)) continue;
|
|
966
|
+
const decimals = p.vault?.asset?.decimals ?? 18;
|
|
967
|
+
const vaultAddress = viem.getAddress(vaultAddr);
|
|
968
|
+
byVault.set(vaultAddress.toLowerCase(), {
|
|
969
|
+
vaultAddress,
|
|
970
|
+
vaultSymbol: (p.vault?.symbol ?? p.vault?.asset?.symbol ?? "Vault").trim() || "Vault",
|
|
971
|
+
underlyingSymbol: (p.vault?.asset?.symbol ?? "").trim() || "\u2014",
|
|
972
|
+
assetsHuman: formatMorphoPositionAssets(rawAssets, decimals),
|
|
973
|
+
assetsUsdLabel: formatMorphoUsd(p.assetsUsd),
|
|
974
|
+
shares: String(p.shares ?? "0").trim()
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
return [...byVault.values()];
|
|
978
|
+
}
|
|
979
|
+
async function fetchMorphoUserBluePositions(args) {
|
|
980
|
+
const user = args.user.trim();
|
|
981
|
+
if (!viem.isAddress(user)) return [];
|
|
982
|
+
const d = await morphoGql(
|
|
983
|
+
`
|
|
984
|
+
query MorphoUserBluePositions($address: String!, $chainId: Int!) {
|
|
985
|
+
userByAddress(address: $address, chainId: $chainId) {
|
|
986
|
+
marketPositions {
|
|
987
|
+
healthFactor
|
|
988
|
+
market {
|
|
989
|
+
marketId
|
|
990
|
+
loanAsset { address symbol decimals }
|
|
991
|
+
collateralAsset { address symbol decimals }
|
|
992
|
+
lltv
|
|
993
|
+
oracle { address }
|
|
994
|
+
irmAddress
|
|
995
|
+
morphoBlue { address }
|
|
996
|
+
state { borrowApy supplyApy borrowAssetsUsd }
|
|
997
|
+
}
|
|
998
|
+
state { collateral borrowAssets }
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
`,
|
|
1003
|
+
{ address: viem.getAddress(user), chainId: args.chainId }
|
|
1004
|
+
);
|
|
1005
|
+
const out = [];
|
|
1006
|
+
for (const p of d.userByAddress?.marketPositions ?? []) {
|
|
1007
|
+
const borrow = (p.state?.borrowAssets ?? "0").trim();
|
|
1008
|
+
const collateral = (p.state?.collateral ?? "0").trim();
|
|
1009
|
+
if ((borrow === "0" || borrow === "0.0") && (collateral === "0" || collateral === "0.0")) continue;
|
|
1010
|
+
const borrowMarket = morphoMarketToBorrowRow(p.market);
|
|
1011
|
+
if (!borrowMarket) continue;
|
|
1012
|
+
const hf = p.healthFactor;
|
|
1013
|
+
out.push({
|
|
1014
|
+
marketId: p.market.marketId,
|
|
1015
|
+
marketLabel: borrowMarket.marketLabel,
|
|
1016
|
+
loanAssetSymbol: borrowMarket.loanAssetSymbol,
|
|
1017
|
+
collateralAssetSymbol: borrowMarket.collateralAssetSymbol,
|
|
1018
|
+
collateralHuman: collateral || "0",
|
|
1019
|
+
borrowHuman: borrow || "0",
|
|
1020
|
+
healthFactorLabel: hf != null && Number.isFinite(hf) ? hf.toFixed(2) : "\u2014",
|
|
1021
|
+
borrowMarket
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
return out;
|
|
1025
|
+
}
|
|
1026
|
+
async function fetchMorphoUserPortfolioUsd(args) {
|
|
1027
|
+
const user = args.user.trim();
|
|
1028
|
+
if (!viem.isAddress(user)) return { vaultUsd: 0, borrowCollateralUsd: 0, totalUsd: 0 };
|
|
1029
|
+
const d = await morphoGql(
|
|
1030
|
+
`
|
|
1031
|
+
query MorphoUserPortfolio($address: String!, $chainId: Int!) {
|
|
1032
|
+
userByAddress(address: $address, chainId: $chainId) {
|
|
1033
|
+
vaultPositions { state { assetsUsd } }
|
|
1034
|
+
vaultV2Positions { assetsUsd }
|
|
1035
|
+
marketPositions { state { collateralUsd borrowAssetsUsd } }
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
`,
|
|
1039
|
+
{ address: viem.getAddress(user), chainId: args.chainId }
|
|
1040
|
+
);
|
|
1041
|
+
let vaultUsd = 0;
|
|
1042
|
+
let borrowCollateralUsd = 0;
|
|
1043
|
+
for (const v of d.userByAddress?.vaultPositions ?? []) {
|
|
1044
|
+
const n = v.state?.assetsUsd;
|
|
1045
|
+
if (n != null && Number.isFinite(n)) vaultUsd += n;
|
|
1046
|
+
}
|
|
1047
|
+
for (const v of d.userByAddress?.vaultV2Positions ?? []) {
|
|
1048
|
+
const n = v.assetsUsd;
|
|
1049
|
+
if (n != null && Number.isFinite(n)) vaultUsd += n;
|
|
1050
|
+
}
|
|
1051
|
+
for (const m of d.userByAddress?.marketPositions ?? []) {
|
|
1052
|
+
const n = m.state?.collateralUsd;
|
|
1053
|
+
if (n != null && Number.isFinite(n)) borrowCollateralUsd += n;
|
|
1054
|
+
}
|
|
1055
|
+
return { vaultUsd, borrowCollateralUsd, totalUsd: vaultUsd + borrowCollateralUsd };
|
|
1056
|
+
}
|
|
1057
|
+
function formatMorphoMarketApySummary(apy) {
|
|
1058
|
+
return formatMorphoApyPct(apy);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// src/protocols/evm/morpho/midnightTypes.ts
|
|
1062
|
+
var MORPHO_MIDNIGHT_UNAVAILABLE_MESSAGE = "Morpho Midnight fixed-rate lending is not yet available on mainnet. This tab will activate after the progressive mainnet rollout.";
|
|
1063
|
+
async function fetchMorphoMidnightIntents(_args) {
|
|
1064
|
+
return [];
|
|
1065
|
+
}
|
|
1066
|
+
var erc4626Abi = viem.parseAbi([
|
|
1067
|
+
"function asset() view returns (address)",
|
|
1068
|
+
"function decimals() view returns (uint8)",
|
|
1069
|
+
"function balanceOf(address account) view returns (uint256)",
|
|
1070
|
+
"function convertToAssets(uint256 shares) view returns (uint256)",
|
|
1071
|
+
"function maxWithdraw(address owner) view returns (uint256)"
|
|
1072
|
+
]);
|
|
1073
|
+
async function fetchMorphoVaultAssetDecimals(args) {
|
|
1074
|
+
const ch = viem.defineChain({
|
|
1075
|
+
id: args.chainId,
|
|
1076
|
+
name: "MorphoVault",
|
|
1077
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1078
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1079
|
+
});
|
|
1080
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl) });
|
|
1081
|
+
const vault = viem.getAddress(args.vault);
|
|
1082
|
+
const assetAddr = await publicClient.readContract({
|
|
1083
|
+
address: vault,
|
|
1084
|
+
abi: erc4626Abi,
|
|
1085
|
+
functionName: "asset"
|
|
1086
|
+
});
|
|
1087
|
+
const d = await publicClient.readContract({
|
|
1088
|
+
address: viem.getAddress(assetAddr),
|
|
1089
|
+
abi: viem.parseAbi(["function decimals() view returns (uint8)"]),
|
|
1090
|
+
functionName: "decimals"
|
|
1091
|
+
});
|
|
1092
|
+
const n = typeof d === "bigint" ? Number(d) : Number(d);
|
|
1093
|
+
return !Number.isFinite(n) || n < 0 || n > 36 ? 18 : n;
|
|
1094
|
+
}
|
|
1095
|
+
async function fetchMorphoVaultMaxWithdrawWei(args) {
|
|
1096
|
+
const ch = viem.defineChain({
|
|
1097
|
+
id: args.chainId,
|
|
1098
|
+
name: "MorphoVault",
|
|
1099
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1100
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1101
|
+
});
|
|
1102
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl) });
|
|
1103
|
+
return publicClient.readContract({
|
|
1104
|
+
address: viem.getAddress(args.vault),
|
|
1105
|
+
abi: erc4626Abi,
|
|
1106
|
+
functionName: "maxWithdraw",
|
|
1107
|
+
args: [viem.getAddress(args.owner)]
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
var morphoBlueAbi = viem.parseAbi([
|
|
1111
|
+
"function position(bytes32 id, address user) view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral)",
|
|
1112
|
+
"function market(bytes32 id) view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)",
|
|
1113
|
+
"function idToMarketParams(bytes32 id) view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)"
|
|
1114
|
+
]);
|
|
1115
|
+
var oracleAbi = viem.parseAbi(["function price() view returns (uint256)"]);
|
|
1116
|
+
var WAD = 10n ** 18n;
|
|
1117
|
+
var ORACLE_PRICE_SCALE = 10n ** 36n;
|
|
1118
|
+
async function previewMorphoBlueHealthAfterCollateralWithdraw(args) {
|
|
1119
|
+
const ch = viem.defineChain({
|
|
1120
|
+
id: args.chainId,
|
|
1121
|
+
name: "MorphoBlue",
|
|
1122
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1123
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1124
|
+
});
|
|
1125
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl) });
|
|
1126
|
+
const morpho = viem.getAddress(args.morphoBlue);
|
|
1127
|
+
const user = viem.getAddress(args.user);
|
|
1128
|
+
const id = args.marketId;
|
|
1129
|
+
const [pos, mkt, params] = await Promise.all([
|
|
1130
|
+
publicClient.readContract({
|
|
1131
|
+
address: morpho,
|
|
1132
|
+
abi: morphoBlueAbi,
|
|
1133
|
+
functionName: "position",
|
|
1134
|
+
args: [id, user]
|
|
1135
|
+
}),
|
|
1136
|
+
publicClient.readContract({
|
|
1137
|
+
address: morpho,
|
|
1138
|
+
abi: morphoBlueAbi,
|
|
1139
|
+
functionName: "market",
|
|
1140
|
+
args: [id]
|
|
1141
|
+
}),
|
|
1142
|
+
publicClient.readContract({
|
|
1143
|
+
address: morpho,
|
|
1144
|
+
abi: morphoBlueAbi,
|
|
1145
|
+
functionName: "idToMarketParams",
|
|
1146
|
+
args: [id]
|
|
1147
|
+
})
|
|
1148
|
+
]);
|
|
1149
|
+
const borrowShares = BigInt(pos[1]);
|
|
1150
|
+
if (borrowShares === 0n) return { healthy: true, healthFactor: null };
|
|
1151
|
+
const collateral = BigInt(pos[2]) - args.withdrawCollateralWei;
|
|
1152
|
+
const oracle = viem.getAddress(params[2]);
|
|
1153
|
+
const lltv = BigInt(params[4]);
|
|
1154
|
+
const price = await publicClient.readContract({
|
|
1155
|
+
address: oracle,
|
|
1156
|
+
abi: oracleAbi,
|
|
1157
|
+
functionName: "price"
|
|
1158
|
+
});
|
|
1159
|
+
const totalBorrowAssets = BigInt(mkt[2]);
|
|
1160
|
+
const totalBorrowShares = BigInt(mkt[3]);
|
|
1161
|
+
if (totalBorrowShares === 0n) return { healthy: true, healthFactor: null };
|
|
1162
|
+
const borrowed = (borrowShares * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares;
|
|
1163
|
+
const maxBorrow = collateral * price / ORACLE_PRICE_SCALE * lltv / WAD;
|
|
1164
|
+
const healthy = maxBorrow >= borrowed;
|
|
1165
|
+
const hf = borrowed === 0n ? null : Number(maxBorrow * WAD / borrowed) / Number(WAD);
|
|
1166
|
+
return { healthy, healthFactor: hf != null && Number.isFinite(hf) ? hf : null };
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// src/core/purpose.ts
|
|
1170
|
+
function mergePurposeText(purposeText, purposeSuffix) {
|
|
1171
|
+
const t = purposeText.trim();
|
|
1172
|
+
const suffix = (purposeSuffix ?? "").trim();
|
|
1173
|
+
if (!suffix) return t;
|
|
1174
|
+
return t ? `${t}
|
|
1175
|
+
|
|
1176
|
+
${suffix}` : suffix;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// src/core/envelope.ts
|
|
1180
|
+
function finalizeMultisign(input) {
|
|
1181
|
+
const { keyGen, destinationChainID, legs } = input;
|
|
1182
|
+
if (legs.length === 0) {
|
|
1183
|
+
throw new Error("finalizeMultisign requires at least one leg");
|
|
1184
|
+
}
|
|
1185
|
+
const ph = (keyGen.pubkeyhex ?? "").trim();
|
|
1186
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
1187
|
+
const keyList = keyGen.keylist ?? [];
|
|
1188
|
+
const clientId = continuumNodeSdk.getClientIdFromKeyGenResult(keyGen);
|
|
1189
|
+
const first = legs[0];
|
|
1190
|
+
const messageHashes = legs.map((l) => l.msgHash);
|
|
1191
|
+
const messageRawBatch = legs.map((l) => l.msgRaw);
|
|
1192
|
+
const batchMeta = legs.map((l) => ({
|
|
1193
|
+
destinationAddress: l.destinationAddress,
|
|
1194
|
+
signatureText: l.signatureText,
|
|
1195
|
+
...l.audit
|
|
1196
|
+
}));
|
|
1197
|
+
const proposalTxParams = legs.map((l) => l.proposalTxParams).filter((p) => p != null && typeof p === "object");
|
|
1198
|
+
const extraPayload = {
|
|
1199
|
+
batchMeta,
|
|
1200
|
+
...input.extraJSON ?? {}
|
|
1201
|
+
};
|
|
1202
|
+
const extraJSON = JSON.stringify(extraPayload);
|
|
1203
|
+
const bodyForSign = {
|
|
1204
|
+
keyList,
|
|
1205
|
+
pubKey: ph,
|
|
1206
|
+
msgHash: messageHashes[0],
|
|
1207
|
+
msgRaw: first.msgRaw,
|
|
1208
|
+
destinationChainID,
|
|
1209
|
+
destinationAddress: input.destinationAddress ?? first.destinationAddress,
|
|
1210
|
+
extraJSON,
|
|
1211
|
+
signatureText: first.signatureText,
|
|
1212
|
+
purpose: mergePurposeText(input.purposeText, input.purposeSuffix),
|
|
1213
|
+
...first.feeSnapshot
|
|
1214
|
+
};
|
|
1215
|
+
if (legs.length > 1) {
|
|
1216
|
+
bodyForSign.messageHashes = messageHashes;
|
|
1217
|
+
bodyForSign.messageRawBatch = messageRawBatch;
|
|
1218
|
+
}
|
|
1219
|
+
if (proposalTxParams.length > 0) {
|
|
1220
|
+
bodyForSign.proposalTxParams = proposalTxParams;
|
|
1221
|
+
}
|
|
1222
|
+
const valueWei = first.valueWei;
|
|
1223
|
+
if (valueWei != null && valueWei > 0n) {
|
|
1224
|
+
bodyForSign.value = valueWei.toString();
|
|
1225
|
+
}
|
|
1226
|
+
if (clientId) bodyForSign.clientId = clientId;
|
|
1227
|
+
return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
|
|
1228
|
+
}
|
|
1229
|
+
function routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimit) {
|
|
1230
|
+
if (chainGasLimit != null && Number.isFinite(chainGasLimit) && chainGasLimit > 0) {
|
|
1231
|
+
return continuumNodeSdk.gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit);
|
|
1232
|
+
}
|
|
1233
|
+
return (estimatedGas * 12n + 9n) / 10n;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// src/chains/evm/buildBatch.ts
|
|
1237
|
+
async function buildEvmMultisignBatch(args) {
|
|
1238
|
+
const { context, steps } = args;
|
|
1239
|
+
const {
|
|
1240
|
+
chainId,
|
|
1241
|
+
rpcUrl,
|
|
1242
|
+
executorAddress,
|
|
1243
|
+
chainDetail,
|
|
1244
|
+
useCustomGas,
|
|
1245
|
+
customGasChainDetails,
|
|
1246
|
+
keyGen,
|
|
1247
|
+
purposeText
|
|
1248
|
+
} = context;
|
|
1249
|
+
if (steps.length === 0) throw new Error("buildEvmMultisignBatch requires at least one step");
|
|
1250
|
+
const ch = viem.defineChain({
|
|
1251
|
+
id: chainId,
|
|
1252
|
+
name: "Destination",
|
|
1253
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1254
|
+
rpcUrls: { default: { http: [rpcUrl] } }
|
|
1255
|
+
});
|
|
1256
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(rpcUrl) });
|
|
1257
|
+
const feeParams = await continuumNodeSdk.fetchChainFeeParams(rpcUrl, chainId);
|
|
1258
|
+
const legacy = Boolean(chainDetail?.legacy) || !feeParams.isEip1559;
|
|
1259
|
+
const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
|
|
1260
|
+
const gasLimitConfig = useCustomGas && chainDetail?.gasLimit != null ? Number(chainDetail.gasLimit) : void 0;
|
|
1261
|
+
const chainGasLimitRouter = chainDetail?.gasLimit != null && Number.isFinite(Number(chainDetail.gasLimit)) && Number(chainDetail.gasLimit) > 0 ? Number(chainDetail.gasLimit) : void 0;
|
|
1262
|
+
const gasFeeMultiplier = useCustomGas && chainDetail?.gasMultiplier != null ? Number(chainDetail.gasMultiplier) : void 0;
|
|
1263
|
+
const executor = viem.getAddress(executorAddress);
|
|
1264
|
+
const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
|
|
1265
|
+
const legs = [];
|
|
1266
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1267
|
+
const step = steps[i];
|
|
1268
|
+
const currentNonce = baseNonce + i;
|
|
1269
|
+
let estimatedGas;
|
|
1270
|
+
if (args.estimateGasForStep) {
|
|
1271
|
+
estimatedGas = await args.estimateGasForStep({ step, index: i, publicClient, executor });
|
|
1272
|
+
} else {
|
|
1273
|
+
try {
|
|
1274
|
+
estimatedGas = await publicClient.estimateGas({
|
|
1275
|
+
to: step.to,
|
|
1276
|
+
data: step.data,
|
|
1277
|
+
value: step.value,
|
|
1278
|
+
account: executor
|
|
1279
|
+
});
|
|
1280
|
+
} catch {
|
|
1281
|
+
estimatedGas = step.fallbackGas ?? 100000n;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
let gasLimitI;
|
|
1285
|
+
if (args.resolveGasLimit) {
|
|
1286
|
+
gasLimitI = await args.resolveGasLimit({ step, index: i, estimatedGas, publicClient });
|
|
1287
|
+
} else if (step.routerSwap) {
|
|
1288
|
+
gasLimitI = routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimitRouter);
|
|
1289
|
+
} else {
|
|
1290
|
+
gasLimitI = useCustomGas ? continuumNodeSdk.gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
|
|
1291
|
+
}
|
|
1292
|
+
let proposalTxParams;
|
|
1293
|
+
let feeSnapshot;
|
|
1294
|
+
let serialized;
|
|
1295
|
+
if (legacy) {
|
|
1296
|
+
let gasPriceWei = await publicClient.getGasPrice();
|
|
1297
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
1298
|
+
gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
1299
|
+
}
|
|
1300
|
+
if (useCustomGas && chainDetail?.gasPrice != null && chainDetail.gasPrice > 0) {
|
|
1301
|
+
const configured = viem.parseGwei(continuumNodeSdk.gweiToDecimalString(Number(chainDetail.gasPrice)));
|
|
1302
|
+
if (configured > gasPriceWei) gasPriceWei = configured;
|
|
1303
|
+
}
|
|
1304
|
+
serialized = viem.serializeTransaction({
|
|
1305
|
+
type: "legacy",
|
|
1306
|
+
to: step.to,
|
|
1307
|
+
data: step.data,
|
|
1308
|
+
value: step.value,
|
|
1309
|
+
gas: gasLimitI,
|
|
1310
|
+
gasPrice: gasPriceWei,
|
|
1311
|
+
nonce: currentNonce,
|
|
1312
|
+
chainId
|
|
1313
|
+
});
|
|
1314
|
+
proposalTxParams = {
|
|
1315
|
+
nonce: currentNonce,
|
|
1316
|
+
gasLimit: gasLimitI.toString(),
|
|
1317
|
+
txType: "legacy",
|
|
1318
|
+
gasPrice: gasPriceWei.toString()
|
|
1319
|
+
};
|
|
1320
|
+
feeSnapshot = continuumNodeSdk.proposalTxParamsToFeeSnapshot(proposalTxParams);
|
|
1321
|
+
} else {
|
|
1322
|
+
const fetchedBase = feeParams.baseFeeGwei ?? 0;
|
|
1323
|
+
const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
|
|
1324
|
+
const configuredBase = useCustomGas && chainDetail?.baseFee != null ? Number(chainDetail.baseFee) : 0;
|
|
1325
|
+
const configuredPriority = useCustomGas && chainDetail?.priorityFee != null ? Number(chainDetail.priorityFee) : 0;
|
|
1326
|
+
const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
|
|
1327
|
+
const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
|
|
1328
|
+
const baseFeeMultiplierPct = useCustomGas && chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(chainDetail.baseFeeMultiplier)) : 100;
|
|
1329
|
+
const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
|
|
1330
|
+
const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
|
|
1331
|
+
let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? viem.parseGwei(continuumNodeSdk.gweiToDecimalString(effectivePriorityFeeGwei)) : viem.parseGwei("1");
|
|
1332
|
+
let maxFeePerGas = viem.parseGwei(continuumNodeSdk.gweiToDecimalString(maxFeePerGasGwei));
|
|
1333
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
1334
|
+
maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
1335
|
+
maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
1336
|
+
}
|
|
1337
|
+
({ maxFeePerGas, maxPriorityFeePerGas } = continuumNodeSdk.alignEip1559FeesWithLatestBase(
|
|
1338
|
+
maxFeePerGas,
|
|
1339
|
+
maxPriorityFeePerGas,
|
|
1340
|
+
latestBaseFeeWei
|
|
1341
|
+
));
|
|
1342
|
+
serialized = viem.serializeTransaction({
|
|
1343
|
+
type: "eip1559",
|
|
1344
|
+
to: step.to,
|
|
1345
|
+
data: step.data,
|
|
1346
|
+
value: step.value,
|
|
1347
|
+
gas: gasLimitI,
|
|
1348
|
+
maxFeePerGas,
|
|
1349
|
+
maxPriorityFeePerGas,
|
|
1350
|
+
nonce: currentNonce,
|
|
1351
|
+
chainId
|
|
1352
|
+
});
|
|
1353
|
+
proposalTxParams = {
|
|
1354
|
+
nonce: currentNonce,
|
|
1355
|
+
gasLimit: gasLimitI.toString(),
|
|
1356
|
+
txType: "eip1559",
|
|
1357
|
+
maxFeePerGas: maxFeePerGas.toString(),
|
|
1358
|
+
maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
1359
|
+
};
|
|
1360
|
+
feeSnapshot = i === 0 ? continuumNodeSdk.proposalTxParamsToFeeSnapshot(proposalTxParams) : {};
|
|
1361
|
+
}
|
|
1362
|
+
const h = viem.keccak256(serialized);
|
|
1363
|
+
const msgHash = h.startsWith("0x") ? h.slice(2) : h;
|
|
1364
|
+
const batchMetaExtra = args.buildBatchMeta({ step, index: i, gasLimit: gasLimitI });
|
|
1365
|
+
legs.push({
|
|
1366
|
+
msgHash,
|
|
1367
|
+
msgRaw: i === 0 && args.firstMsgRawNo0x != null ? args.firstMsgRawNo0x : serialized,
|
|
1368
|
+
destinationAddress: step.to,
|
|
1369
|
+
signatureText: typeof batchMetaExtra.signatureText === "string" ? batchMetaExtra.signatureText : JSON.stringify(batchMetaExtra.signatureText ?? {}),
|
|
1370
|
+
audit: batchMetaExtra,
|
|
1371
|
+
feeSnapshot: i === 0 ? feeSnapshot : {},
|
|
1372
|
+
proposalTxParams,
|
|
1373
|
+
valueWei: i === 0 ? step.value : void 0
|
|
1374
|
+
});
|
|
1375
|
+
if (i === 0 && args.firstMsgRawNo0x != null) {
|
|
1376
|
+
legs[0].msgRaw = args.firstMsgRawNo0x;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
const extraJSON = {};
|
|
1380
|
+
if (useCustomGas && customGasChainDetails && Object.keys(customGasChainDetails).length > 0) {
|
|
1381
|
+
extraJSON.customGasChainDetails = customGasChainDetails;
|
|
1382
|
+
}
|
|
1383
|
+
const result = finalizeMultisign({
|
|
1384
|
+
keyGen,
|
|
1385
|
+
purposeText,
|
|
1386
|
+
purposeSuffix: args.purposeSuffix,
|
|
1387
|
+
destinationChainID: String(chainId),
|
|
1388
|
+
destinationAddress: args.destinationAddress ?? steps[0].to,
|
|
1389
|
+
legs,
|
|
1390
|
+
extraJSON: Object.keys(extraJSON).length > 0 ? extraJSON : void 0
|
|
1391
|
+
});
|
|
1392
|
+
const pv = args.payableValueWei;
|
|
1393
|
+
if (pv != null && pv > 0n) {
|
|
1394
|
+
result.bodyForSign.value = pv.toString();
|
|
1395
|
+
}
|
|
1396
|
+
return result;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// src/protocols/evm/morpho/vaultDepositMultisign.ts
|
|
1400
|
+
var MORPHO_VAULT_DEPOSIT_FALLBACK_GAS = 950000n;
|
|
1401
|
+
var MORPHO_ERC20_APPROVE_FALLBACK = 100000n;
|
|
1402
|
+
var MORPHO_WETH_DEPOSIT_FALLBACK = 120000n;
|
|
1403
|
+
var wethDepositAbi = viem.parseAbi(["function deposit() payable"]);
|
|
1404
|
+
var erc20AllowanceAbi = viem.parseAbi([
|
|
1405
|
+
"function allowance(address owner, address spender) view returns (uint256)"
|
|
1406
|
+
]);
|
|
1407
|
+
var erc20ApproveAbi = viem.parseAbi(["function approve(address spender, uint256 amount) returns (bool)"]);
|
|
1408
|
+
var vaultDepositAbi = viem.parseAbi([
|
|
1409
|
+
"function deposit(uint256 assets, address onBehalf) returns (uint256 shares)"
|
|
1410
|
+
]);
|
|
1411
|
+
async function buildEvmMultisignBodyMorphoVaultDepositBatch(args) {
|
|
1412
|
+
const asset = viem.getAddress(args.underlying);
|
|
1413
|
+
const vault = viem.getAddress(args.vault);
|
|
1414
|
+
const weth = viem.getAddress(args.nativeWrapped);
|
|
1415
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
1416
|
+
const onBehalf = viem.getAddress(args.onBehalf);
|
|
1417
|
+
if (args.isNativeIn && asset.toLowerCase() !== weth.toLowerCase()) {
|
|
1418
|
+
throw new Error("Native deposit path: underlying asset must match the chain wrapped native token.");
|
|
1419
|
+
}
|
|
1420
|
+
const ch = viem.defineChain({
|
|
1421
|
+
id: args.chainId,
|
|
1422
|
+
name: "MorphoVaultDeposit",
|
|
1423
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1424
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1425
|
+
});
|
|
1426
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl) });
|
|
1427
|
+
const dec = await fetchMorphoVaultAssetDecimals({ rpcUrl: args.rpcUrl, chainId: args.chainId, vault });
|
|
1428
|
+
const amountWei = viem.parseUnits(args.amountHuman, dec);
|
|
1429
|
+
if (amountWei === 0n) throw new Error("Deposit amount is zero after converting with token decimals.");
|
|
1430
|
+
const steps = [];
|
|
1431
|
+
if (args.isNativeIn) {
|
|
1432
|
+
const wethData = viem.encodeFunctionData({ abi: wethDepositAbi, functionName: "deposit" });
|
|
1433
|
+
steps.push({ kind: "weth_deposit", to: weth, data: wethData, value: amountWei });
|
|
1434
|
+
}
|
|
1435
|
+
const spendToken = args.isNativeIn ? weth : asset;
|
|
1436
|
+
const currentAllowance = await publicClient.readContract({
|
|
1437
|
+
address: spendToken,
|
|
1438
|
+
abi: erc20AllowanceAbi,
|
|
1439
|
+
functionName: "allowance",
|
|
1440
|
+
args: [executor, vault]
|
|
1441
|
+
});
|
|
1442
|
+
if (currentAllowance < amountWei) {
|
|
1443
|
+
if (currentAllowance > 0n) {
|
|
1444
|
+
steps.push({
|
|
1445
|
+
kind: "approve",
|
|
1446
|
+
to: spendToken,
|
|
1447
|
+
data: viem.encodeFunctionData({ abi: erc20ApproveAbi, functionName: "approve", args: [vault, 0n] }),
|
|
1448
|
+
value: 0n
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
steps.push({
|
|
1452
|
+
kind: "approve",
|
|
1453
|
+
to: spendToken,
|
|
1454
|
+
data: viem.encodeFunctionData({ abi: erc20ApproveAbi, functionName: "approve", args: [vault, amountWei] }),
|
|
1455
|
+
value: 0n
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
const depositData = viem.encodeFunctionData({
|
|
1459
|
+
abi: vaultDepositAbi,
|
|
1460
|
+
functionName: "deposit",
|
|
1461
|
+
args: [amountWei, onBehalf]
|
|
1462
|
+
});
|
|
1463
|
+
steps.push({ kind: "vault_deposit", to: vault, data: depositData, value: 0n });
|
|
1464
|
+
const vaultLabel = (args.vaultMarketLabel ?? "").trim() || "Morpho vault";
|
|
1465
|
+
const purposeSuffix = `Morpho: ${steps.length}-tx \u2014 VaultV2.deposit (${args.amountHuman} underlying) into "${vaultLabel}".`;
|
|
1466
|
+
const evmSteps = steps.map((s) => ({
|
|
1467
|
+
to: s.to,
|
|
1468
|
+
data: s.data,
|
|
1469
|
+
value: s.value,
|
|
1470
|
+
fallbackGas: s.kind === "weth_deposit" ? MORPHO_WETH_DEPOSIT_FALLBACK : s.kind === "approve" ? MORPHO_ERC20_APPROVE_FALLBACK : MORPHO_VAULT_DEPOSIT_FALLBACK_GAS
|
|
1471
|
+
}));
|
|
1472
|
+
const firstDataNo0x = steps[0].data.startsWith("0x") ? steps[0].data.slice(2) : steps[0].data;
|
|
1473
|
+
return buildEvmMultisignBatch({
|
|
1474
|
+
context: {
|
|
1475
|
+
chainCategory: "evm",
|
|
1476
|
+
keyGen: args.keyGen,
|
|
1477
|
+
purposeText: args.purposeText,
|
|
1478
|
+
chainId: args.chainId,
|
|
1479
|
+
rpcUrl: args.rpcUrl,
|
|
1480
|
+
executorAddress: executor,
|
|
1481
|
+
chainDetail: args.chainDetail,
|
|
1482
|
+
useCustomGas: args.useCustomGas,
|
|
1483
|
+
customGasChainDetails: args.customGasChainDetails
|
|
1484
|
+
},
|
|
1485
|
+
steps: evmSteps,
|
|
1486
|
+
purposeSuffix,
|
|
1487
|
+
firstMsgRawNo0x: firstDataNo0x,
|
|
1488
|
+
destinationAddress: vault,
|
|
1489
|
+
buildBatchMeta: ({ gasLimit }) => ({
|
|
1490
|
+
signatureText: JSON.stringify({
|
|
1491
|
+
kind: "Morpho",
|
|
1492
|
+
name: "VaultV2.deposit",
|
|
1493
|
+
vault,
|
|
1494
|
+
onBehalf,
|
|
1495
|
+
vaultMarket: vaultLabel,
|
|
1496
|
+
amountHuman: args.amountHuman
|
|
1497
|
+
}),
|
|
1498
|
+
evm: { type: "morpho_vault_deposit", version: 1, chainId: String(args.chainId) },
|
|
1499
|
+
morpho: {
|
|
1500
|
+
vaultMarket: vaultLabel,
|
|
1501
|
+
amountHuman: args.amountHuman,
|
|
1502
|
+
vault,
|
|
1503
|
+
onBehalf,
|
|
1504
|
+
gasBuildDeposit: { baseGasUnits: gasLimit.toString() }
|
|
1505
|
+
}
|
|
1506
|
+
})
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
var MORPHO_VAULT_WITHDRAW_FALLBACK_GAS = 900000n;
|
|
1510
|
+
var vaultWithdrawAbi = viem.parseAbi([
|
|
1511
|
+
"function withdraw(uint256 assets, address receiver, address onBehalf) returns (uint256 shares)"
|
|
1512
|
+
]);
|
|
1513
|
+
async function buildEvmMultisignBodyMorphoVaultWithdraw(args) {
|
|
1514
|
+
const vault = viem.getAddress(args.vault);
|
|
1515
|
+
const receiver = viem.getAddress(args.receiver);
|
|
1516
|
+
const shareOwner = viem.getAddress(args.vaultShareOwner ?? args.receiver);
|
|
1517
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
1518
|
+
const dec = await fetchMorphoVaultAssetDecimals({ rpcUrl: args.rpcUrl, chainId: args.chainId, vault });
|
|
1519
|
+
const amountWei = viem.parseUnits(args.amountHuman, dec);
|
|
1520
|
+
if (amountWei === 0n) throw new Error("Withdraw amount is zero after converting with token decimals.");
|
|
1521
|
+
const maxW = await fetchMorphoVaultMaxWithdrawWei({
|
|
1522
|
+
rpcUrl: args.rpcUrl,
|
|
1523
|
+
chainId: args.chainId,
|
|
1524
|
+
vault,
|
|
1525
|
+
owner: shareOwner
|
|
1526
|
+
});
|
|
1527
|
+
if (amountWei > maxW) {
|
|
1528
|
+
throw new Error("Withdraw amount exceeds maxWithdraw for this vault. Try a smaller amount.");
|
|
1529
|
+
}
|
|
1530
|
+
const withdrawData = viem.encodeFunctionData({
|
|
1531
|
+
abi: vaultWithdrawAbi,
|
|
1532
|
+
functionName: "withdraw",
|
|
1533
|
+
args: [amountWei, receiver, shareOwner]
|
|
1534
|
+
});
|
|
1535
|
+
const vaultLabel = (args.vaultMarketLabel ?? "").trim() || "Morpho vault";
|
|
1536
|
+
const purposeSuffix = `Morpho: 1-tx \u2014 VaultV2.withdraw (${args.amountHuman} underlying) from "${vaultLabel}".`;
|
|
1537
|
+
const evmSteps = [
|
|
1538
|
+
{ to: vault, data: withdrawData, value: 0n, fallbackGas: MORPHO_VAULT_WITHDRAW_FALLBACK_GAS }
|
|
1539
|
+
];
|
|
1540
|
+
const firstDataNo0x = withdrawData.startsWith("0x") ? withdrawData.slice(2) : withdrawData;
|
|
1541
|
+
return buildEvmMultisignBatch({
|
|
1542
|
+
context: {
|
|
1543
|
+
chainCategory: "evm",
|
|
1544
|
+
keyGen: args.keyGen,
|
|
1545
|
+
purposeText: args.purposeText,
|
|
1546
|
+
chainId: args.chainId,
|
|
1547
|
+
rpcUrl: args.rpcUrl,
|
|
1548
|
+
executorAddress: executor,
|
|
1549
|
+
chainDetail: args.chainDetail,
|
|
1550
|
+
useCustomGas: args.useCustomGas,
|
|
1551
|
+
customGasChainDetails: args.customGasChainDetails
|
|
1552
|
+
},
|
|
1553
|
+
steps: evmSteps,
|
|
1554
|
+
purposeSuffix,
|
|
1555
|
+
firstMsgRawNo0x: firstDataNo0x,
|
|
1556
|
+
destinationAddress: vault,
|
|
1557
|
+
buildBatchMeta: ({ gasLimit }) => ({
|
|
1558
|
+
signatureText: JSON.stringify({
|
|
1559
|
+
kind: "Morpho",
|
|
1560
|
+
name: "VaultV2.withdraw",
|
|
1561
|
+
vault,
|
|
1562
|
+
receiver,
|
|
1563
|
+
owner: shareOwner,
|
|
1564
|
+
vaultMarket: vaultLabel,
|
|
1565
|
+
amountHuman: args.amountHuman
|
|
1566
|
+
}),
|
|
1567
|
+
evm: { type: "morpho_vault_withdraw", version: 1, chainId: String(args.chainId) },
|
|
1568
|
+
morpho: {
|
|
1569
|
+
vaultMarket: vaultLabel,
|
|
1570
|
+
amountHuman: args.amountHuman,
|
|
1571
|
+
vault,
|
|
1572
|
+
owner: shareOwner,
|
|
1573
|
+
gasBuildWithdraw: { baseGasUnits: gasLimit.toString() }
|
|
1574
|
+
}
|
|
1575
|
+
})
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
var MORPHO_BLUE_COLLATERAL_DEPOSIT_FALLBACK_GAS = 1200000n;
|
|
1579
|
+
var MORPHO_BLUE_BORROW_FALLBACK_GAS = 1100000n;
|
|
1580
|
+
var MORPHO_BLUE_REPAY_FALLBACK_GAS = 1000000n;
|
|
1581
|
+
var MORPHO_BLUE_COLLATERAL_WITHDRAW_FALLBACK_GAS = 900000n;
|
|
1582
|
+
var MORPHO_ERC20_APPROVE_FALLBACK2 = 100000n;
|
|
1583
|
+
var MORPHO_WETH_DEPOSIT_FALLBACK2 = 120000n;
|
|
1584
|
+
var wethDepositAbi2 = viem.parseAbi(["function deposit() payable"]);
|
|
1585
|
+
var erc20AllowanceAbi2 = viem.parseAbi([
|
|
1586
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
1587
|
+
"function decimals() view returns (uint8)"
|
|
1588
|
+
]);
|
|
1589
|
+
var erc20ApproveAbi2 = viem.parseAbi(["function approve(address spender, uint256 amount) returns (bool)"]);
|
|
1590
|
+
var morphoBlueAbi2 = viem.parseAbi([
|
|
1591
|
+
"function supplyCollateral((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv), uint256 assets, address onBehalf, bytes data)",
|
|
1592
|
+
"function borrow((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv), uint256 assets, uint256 shares, address onBehalf, address receiver)",
|
|
1593
|
+
"function repay((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv), uint256 assets, uint256 shares, address onBehalf, bytes data)",
|
|
1594
|
+
"function withdrawCollateral((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv), uint256 assets, address onBehalf, address receiver)"
|
|
1595
|
+
]);
|
|
1596
|
+
function marketParamsTuple(p) {
|
|
1597
|
+
return {
|
|
1598
|
+
loanToken: p.loanToken,
|
|
1599
|
+
collateralToken: p.collateralToken,
|
|
1600
|
+
oracle: p.oracle,
|
|
1601
|
+
irm: p.irm,
|
|
1602
|
+
lltv: p.lltv
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
async function readTokenDecimals(args) {
|
|
1606
|
+
const ch = viem.defineChain({
|
|
1607
|
+
id: args.chainId,
|
|
1608
|
+
name: "MorphoToken",
|
|
1609
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1610
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1611
|
+
});
|
|
1612
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl) });
|
|
1613
|
+
const d = await publicClient.readContract({
|
|
1614
|
+
address: args.token,
|
|
1615
|
+
abi: erc20AllowanceAbi2,
|
|
1616
|
+
functionName: "decimals"
|
|
1617
|
+
});
|
|
1618
|
+
const n = typeof d === "bigint" ? Number(d) : Number(d);
|
|
1619
|
+
return !Number.isFinite(n) || n < 0 || n > 36 ? 18 : n;
|
|
1620
|
+
}
|
|
1621
|
+
async function buildApproveSteps(args) {
|
|
1622
|
+
const steps = [];
|
|
1623
|
+
let spendToken = args.token;
|
|
1624
|
+
if (args.isNativeIn && args.nativeWrapped) {
|
|
1625
|
+
const weth = viem.getAddress(args.nativeWrapped);
|
|
1626
|
+
if (spendToken.toLowerCase() !== weth.toLowerCase()) {
|
|
1627
|
+
throw new Error("Native collateral path: token must match wrapped native.");
|
|
1628
|
+
}
|
|
1629
|
+
const wethData = viem.encodeFunctionData({ abi: wethDepositAbi2, functionName: "deposit" });
|
|
1630
|
+
steps.push({ kind: "weth_deposit", to: weth, data: wethData, value: args.amountWei });
|
|
1631
|
+
spendToken = weth;
|
|
1632
|
+
}
|
|
1633
|
+
const ch = viem.defineChain({
|
|
1634
|
+
id: args.chainId,
|
|
1635
|
+
name: "MorphoApprove",
|
|
1636
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1637
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1638
|
+
});
|
|
1639
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl) });
|
|
1640
|
+
const currentAllowance = await publicClient.readContract({
|
|
1641
|
+
address: spendToken,
|
|
1642
|
+
abi: erc20AllowanceAbi2,
|
|
1643
|
+
functionName: "allowance",
|
|
1644
|
+
args: [args.executor, args.spender]
|
|
1645
|
+
});
|
|
1646
|
+
if (currentAllowance < args.amountWei) {
|
|
1647
|
+
if (currentAllowance > 0n) {
|
|
1648
|
+
steps.push({
|
|
1649
|
+
kind: "approve",
|
|
1650
|
+
to: spendToken,
|
|
1651
|
+
data: viem.encodeFunctionData({ abi: erc20ApproveAbi2, functionName: "approve", args: [args.spender, 0n] }),
|
|
1652
|
+
value: 0n
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
steps.push({
|
|
1656
|
+
kind: "approve",
|
|
1657
|
+
to: spendToken,
|
|
1658
|
+
data: viem.encodeFunctionData({ abi: erc20ApproveAbi2, functionName: "approve", args: [args.spender, args.amountWei] }),
|
|
1659
|
+
value: 0n
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
return steps;
|
|
1663
|
+
}
|
|
1664
|
+
function stepsToEvm(steps, final) {
|
|
1665
|
+
return [
|
|
1666
|
+
...steps.map((s) => ({
|
|
1667
|
+
to: s.to,
|
|
1668
|
+
data: s.data,
|
|
1669
|
+
value: s.value,
|
|
1670
|
+
fallbackGas: s.kind === "weth_deposit" ? MORPHO_WETH_DEPOSIT_FALLBACK2 : MORPHO_ERC20_APPROVE_FALLBACK2
|
|
1671
|
+
})),
|
|
1672
|
+
final
|
|
1673
|
+
];
|
|
1674
|
+
}
|
|
1675
|
+
async function buildEvmMultisignBodyMorphoBlueSupplyCollateralBatch(args) {
|
|
1676
|
+
const morphoBlue = viem.getAddress(args.morphoBlue);
|
|
1677
|
+
const onBehalf = viem.getAddress(args.onBehalf);
|
|
1678
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
1679
|
+
const collateral = viem.getAddress(args.collateralToken);
|
|
1680
|
+
const dec = await readTokenDecimals({ rpcUrl: args.rpcUrl, chainId: args.chainId, token: collateral });
|
|
1681
|
+
const amountWei = viem.parseUnits(args.amountHuman, dec);
|
|
1682
|
+
if (amountWei === 0n) throw new Error("Collateral amount is zero.");
|
|
1683
|
+
const approveSteps = await buildApproveSteps({
|
|
1684
|
+
rpcUrl: args.rpcUrl,
|
|
1685
|
+
chainId: args.chainId,
|
|
1686
|
+
token: collateral,
|
|
1687
|
+
spender: morphoBlue,
|
|
1688
|
+
amountWei,
|
|
1689
|
+
executor,
|
|
1690
|
+
isNativeIn: args.isNativeIn,
|
|
1691
|
+
nativeWrapped: args.nativeWrapped
|
|
1692
|
+
});
|
|
1693
|
+
const supplyData = viem.encodeFunctionData({
|
|
1694
|
+
abi: morphoBlueAbi2,
|
|
1695
|
+
functionName: "supplyCollateral",
|
|
1696
|
+
args: [marketParamsTuple(args.marketParams), amountWei, onBehalf, "0x"]
|
|
1697
|
+
});
|
|
1698
|
+
const marketLabel = (args.marketLabel ?? "").trim() || "Morpho Blue market";
|
|
1699
|
+
const evmSteps = stepsToEvm(approveSteps, {
|
|
1700
|
+
to: morphoBlue,
|
|
1701
|
+
data: supplyData,
|
|
1702
|
+
value: 0n,
|
|
1703
|
+
fallbackGas: MORPHO_BLUE_COLLATERAL_DEPOSIT_FALLBACK_GAS
|
|
1704
|
+
});
|
|
1705
|
+
return buildEvmMultisignBatch({
|
|
1706
|
+
context: {
|
|
1707
|
+
chainCategory: "evm",
|
|
1708
|
+
keyGen: args.keyGen,
|
|
1709
|
+
purposeText: args.purposeText,
|
|
1710
|
+
chainId: args.chainId,
|
|
1711
|
+
rpcUrl: args.rpcUrl,
|
|
1712
|
+
executorAddress: executor,
|
|
1713
|
+
chainDetail: args.chainDetail,
|
|
1714
|
+
useCustomGas: args.useCustomGas,
|
|
1715
|
+
customGasChainDetails: args.customGasChainDetails
|
|
1716
|
+
},
|
|
1717
|
+
steps: evmSteps,
|
|
1718
|
+
purposeSuffix: `Morpho Blue: supplyCollateral (${args.amountHuman}) for "${marketLabel}".`,
|
|
1719
|
+
firstMsgRawNo0x: evmSteps[0].data.slice(2),
|
|
1720
|
+
destinationAddress: morphoBlue,
|
|
1721
|
+
buildBatchMeta: ({ gasLimit }) => ({
|
|
1722
|
+
signatureText: JSON.stringify({ kind: "MorphoBlue", name: "supplyCollateral", marketLabel, amountHuman: args.amountHuman }),
|
|
1723
|
+
evm: { type: "morpho_blue_supply_collateral", version: 1, chainId: String(args.chainId) },
|
|
1724
|
+
morpho: { marketLabel, amountHuman: args.amountHuman, gasBuild: { baseGasUnits: gasLimit.toString() } }
|
|
1725
|
+
})
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
async function buildEvmMultisignBodyMorphoBlueBorrowBatch(args) {
|
|
1729
|
+
const morphoBlue = viem.getAddress(args.morphoBlue);
|
|
1730
|
+
const onBehalf = viem.getAddress(args.onBehalf);
|
|
1731
|
+
const receiver = viem.getAddress(args.receiver);
|
|
1732
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
1733
|
+
const loan = viem.getAddress(args.loanToken);
|
|
1734
|
+
const dec = await readTokenDecimals({ rpcUrl: args.rpcUrl, chainId: args.chainId, token: loan });
|
|
1735
|
+
const amountWei = viem.parseUnits(args.amountHuman, dec);
|
|
1736
|
+
if (amountWei === 0n) throw new Error("Borrow amount is zero.");
|
|
1737
|
+
const borrowData = viem.encodeFunctionData({
|
|
1738
|
+
abi: morphoBlueAbi2,
|
|
1739
|
+
functionName: "borrow",
|
|
1740
|
+
args: [marketParamsTuple(args.marketParams), amountWei, 0n, onBehalf, receiver]
|
|
1741
|
+
});
|
|
1742
|
+
const marketLabel = (args.marketLabel ?? "").trim() || "Morpho Blue market";
|
|
1743
|
+
const evmSteps = [
|
|
1744
|
+
{ to: morphoBlue, data: borrowData, value: 0n, fallbackGas: MORPHO_BLUE_BORROW_FALLBACK_GAS }
|
|
1745
|
+
];
|
|
1746
|
+
return buildEvmMultisignBatch({
|
|
1747
|
+
context: {
|
|
1748
|
+
chainCategory: "evm",
|
|
1749
|
+
keyGen: args.keyGen,
|
|
1750
|
+
purposeText: args.purposeText,
|
|
1751
|
+
chainId: args.chainId,
|
|
1752
|
+
rpcUrl: args.rpcUrl,
|
|
1753
|
+
executorAddress: executor,
|
|
1754
|
+
chainDetail: args.chainDetail,
|
|
1755
|
+
useCustomGas: args.useCustomGas,
|
|
1756
|
+
customGasChainDetails: args.customGasChainDetails
|
|
1757
|
+
},
|
|
1758
|
+
steps: evmSteps,
|
|
1759
|
+
purposeSuffix: `Morpho Blue: borrow (${args.amountHuman}) from "${marketLabel}".`,
|
|
1760
|
+
firstMsgRawNo0x: borrowData.slice(2),
|
|
1761
|
+
destinationAddress: morphoBlue,
|
|
1762
|
+
buildBatchMeta: ({ gasLimit }) => ({
|
|
1763
|
+
signatureText: JSON.stringify({ kind: "MorphoBlue", name: "borrow", marketLabel, amountHuman: args.amountHuman }),
|
|
1764
|
+
evm: { type: "morpho_blue_borrow", version: 1, chainId: String(args.chainId) },
|
|
1765
|
+
morpho: { marketLabel, amountHuman: args.amountHuman, gasBuild: { baseGasUnits: gasLimit.toString() } }
|
|
1766
|
+
})
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
async function buildEvmMultisignBodyMorphoBlueRepayBatch(args) {
|
|
1770
|
+
const morphoBlue = viem.getAddress(args.morphoBlue);
|
|
1771
|
+
const onBehalf = viem.getAddress(args.onBehalf);
|
|
1772
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
1773
|
+
const loan = viem.getAddress(args.loanToken);
|
|
1774
|
+
const dec = await readTokenDecimals({ rpcUrl: args.rpcUrl, chainId: args.chainId, token: loan });
|
|
1775
|
+
const amountWei = viem.parseUnits(args.amountHuman, dec);
|
|
1776
|
+
if (amountWei === 0n) throw new Error("Repay amount is zero.");
|
|
1777
|
+
const approveSteps = await buildApproveSteps({
|
|
1778
|
+
rpcUrl: args.rpcUrl,
|
|
1779
|
+
chainId: args.chainId,
|
|
1780
|
+
token: loan,
|
|
1781
|
+
spender: morphoBlue,
|
|
1782
|
+
amountWei,
|
|
1783
|
+
executor
|
|
1784
|
+
});
|
|
1785
|
+
const repayData = viem.encodeFunctionData({
|
|
1786
|
+
abi: morphoBlueAbi2,
|
|
1787
|
+
functionName: "repay",
|
|
1788
|
+
args: [marketParamsTuple(args.marketParams), amountWei, 0n, onBehalf, "0x"]
|
|
1789
|
+
});
|
|
1790
|
+
const marketLabel = (args.marketLabel ?? "").trim() || "Morpho Blue market";
|
|
1791
|
+
const evmSteps = stepsToEvm(approveSteps, {
|
|
1792
|
+
to: morphoBlue,
|
|
1793
|
+
data: repayData,
|
|
1794
|
+
value: 0n,
|
|
1795
|
+
fallbackGas: MORPHO_BLUE_REPAY_FALLBACK_GAS
|
|
1796
|
+
});
|
|
1797
|
+
return buildEvmMultisignBatch({
|
|
1798
|
+
context: {
|
|
1799
|
+
chainCategory: "evm",
|
|
1800
|
+
keyGen: args.keyGen,
|
|
1801
|
+
purposeText: args.purposeText,
|
|
1802
|
+
chainId: args.chainId,
|
|
1803
|
+
rpcUrl: args.rpcUrl,
|
|
1804
|
+
executorAddress: executor,
|
|
1805
|
+
chainDetail: args.chainDetail,
|
|
1806
|
+
useCustomGas: args.useCustomGas,
|
|
1807
|
+
customGasChainDetails: args.customGasChainDetails
|
|
1808
|
+
},
|
|
1809
|
+
steps: evmSteps,
|
|
1810
|
+
purposeSuffix: `Morpho Blue: repay (${args.amountHuman}) for "${marketLabel}".`,
|
|
1811
|
+
firstMsgRawNo0x: evmSteps[0].data.slice(2),
|
|
1812
|
+
destinationAddress: morphoBlue,
|
|
1813
|
+
buildBatchMeta: ({ gasLimit }) => ({
|
|
1814
|
+
signatureText: JSON.stringify({ kind: "MorphoBlue", name: "repay", marketLabel, amountHuman: args.amountHuman }),
|
|
1815
|
+
evm: { type: "morpho_blue_repay", version: 1, chainId: String(args.chainId) },
|
|
1816
|
+
morpho: { marketLabel, amountHuman: args.amountHuman, gasBuild: { baseGasUnits: gasLimit.toString() } }
|
|
1817
|
+
})
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
async function buildEvmMultisignBodyMorphoBlueWithdrawCollateralBatch(args) {
|
|
1821
|
+
const morphoBlue = viem.getAddress(args.morphoBlue);
|
|
1822
|
+
const onBehalf = viem.getAddress(args.onBehalf);
|
|
1823
|
+
const receiver = viem.getAddress(args.receiver);
|
|
1824
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
1825
|
+
const amountWei = viem.parseUnits(args.amountHuman, args.collateralDecimals);
|
|
1826
|
+
if (amountWei === 0n) throw new Error("Collateral withdraw amount is zero.");
|
|
1827
|
+
const withdrawData = viem.encodeFunctionData({
|
|
1828
|
+
abi: morphoBlueAbi2,
|
|
1829
|
+
functionName: "withdrawCollateral",
|
|
1830
|
+
args: [marketParamsTuple(args.marketParams), amountWei, onBehalf, receiver]
|
|
1831
|
+
});
|
|
1832
|
+
const marketLabel = (args.marketLabel ?? "").trim() || "Morpho Blue market";
|
|
1833
|
+
const evmSteps = [
|
|
1834
|
+
{ to: morphoBlue, data: withdrawData, value: 0n, fallbackGas: MORPHO_BLUE_COLLATERAL_WITHDRAW_FALLBACK_GAS }
|
|
1835
|
+
];
|
|
1836
|
+
return buildEvmMultisignBatch({
|
|
1837
|
+
context: {
|
|
1838
|
+
chainCategory: "evm",
|
|
1839
|
+
keyGen: args.keyGen,
|
|
1840
|
+
purposeText: args.purposeText,
|
|
1841
|
+
chainId: args.chainId,
|
|
1842
|
+
rpcUrl: args.rpcUrl,
|
|
1843
|
+
executorAddress: executor,
|
|
1844
|
+
chainDetail: args.chainDetail,
|
|
1845
|
+
useCustomGas: args.useCustomGas,
|
|
1846
|
+
customGasChainDetails: args.customGasChainDetails
|
|
1847
|
+
},
|
|
1848
|
+
steps: evmSteps,
|
|
1849
|
+
purposeSuffix: `Morpho Blue: withdrawCollateral (${args.amountHuman}) from "${marketLabel}".`,
|
|
1850
|
+
firstMsgRawNo0x: withdrawData.slice(2),
|
|
1851
|
+
destinationAddress: morphoBlue,
|
|
1852
|
+
buildBatchMeta: ({ gasLimit }) => ({
|
|
1853
|
+
signatureText: JSON.stringify({ kind: "MorphoBlue", name: "withdrawCollateral", marketLabel, amountHuman: args.amountHuman }),
|
|
1854
|
+
evm: { type: "morpho_blue_withdraw_collateral", version: 1, chainId: String(args.chainId) },
|
|
1855
|
+
morpho: { marketLabel, amountHuman: args.amountHuman, gasBuild: { baseGasUnits: gasLimit.toString() } }
|
|
1856
|
+
})
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
var MORPHO_MERKL_CLAIM_FALLBACK_GAS = 500000n;
|
|
1860
|
+
async function buildEvmMultisignBodyMorphoMerklDistributorClaim(args) {
|
|
1861
|
+
const to = viem.getAddress(args.to);
|
|
1862
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
1863
|
+
const firstDataNo0x = args.data.startsWith("0x") ? args.data.slice(2) : args.data;
|
|
1864
|
+
return buildEvmMultisignBatch({
|
|
1865
|
+
context: {
|
|
1866
|
+
chainCategory: "evm",
|
|
1867
|
+
keyGen: args.keyGen,
|
|
1868
|
+
purposeText: args.purposeText,
|
|
1869
|
+
chainId: args.chainId,
|
|
1870
|
+
rpcUrl: args.rpcUrl,
|
|
1871
|
+
executorAddress: executor,
|
|
1872
|
+
chainDetail: args.chainDetail,
|
|
1873
|
+
useCustomGas: args.useCustomGas,
|
|
1874
|
+
customGasChainDetails: args.customGasChainDetails
|
|
1875
|
+
},
|
|
1876
|
+
steps: [{ to, data: args.data, value: args.valueWei, fallbackGas: MORPHO_MERKL_CLAIM_FALLBACK_GAS }],
|
|
1877
|
+
purposeSuffix: `Morpho: 1-tx \u2014 Merkl distributor claim (${args.claimLeafCount} reward(s)).`,
|
|
1878
|
+
firstMsgRawNo0x: firstDataNo0x,
|
|
1879
|
+
destinationAddress: to,
|
|
1880
|
+
buildBatchMeta: ({ gasLimit }) => ({
|
|
1881
|
+
signatureText: JSON.stringify({
|
|
1882
|
+
kind: "Morpho",
|
|
1883
|
+
name: "Merkl Distributor claim",
|
|
1884
|
+
claimLeafCount: args.claimLeafCount,
|
|
1885
|
+
chainId: args.chainId
|
|
1886
|
+
}),
|
|
1887
|
+
evm: { type: "morpho_merkl_distributor_claim", version: 1, chainId: String(args.chainId) },
|
|
1888
|
+
morpho: { step: "merkl_distributor_claim", claimLeafCount: args.claimLeafCount, gasBuildClaim: { baseGasUnits: gasLimit.toString() } }
|
|
1889
|
+
})
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
// src/protocols/evm/morpho/index.ts
|
|
1894
|
+
var MORPHO_PROTOCOL_ID = "morpho";
|
|
1895
|
+
var morphoProtocolModule = {
|
|
1896
|
+
id: MORPHO_PROTOCOL_ID,
|
|
1897
|
+
chainCategory: "evm",
|
|
1898
|
+
isChainSupported(ctx) {
|
|
1899
|
+
return ctx.chainCategory === "evm";
|
|
1900
|
+
},
|
|
1901
|
+
isTokenSupported(token) {
|
|
1902
|
+
return token.category === "evm" && (token.kind === "native" || token.kind === "erc20");
|
|
1903
|
+
},
|
|
1904
|
+
actions: [
|
|
1905
|
+
{ id: "morpho.fetch-earn-vaults", protocolId: MORPHO_PROTOCOL_ID, chainCategory: "evm", description: "Search Morpho-listed earn vaults by asset or name", commonParams: [], params: {} },
|
|
1906
|
+
{ id: "morpho.vault-deposit", protocolId: MORPHO_PROTOCOL_ID, chainCategory: "evm", description: "Deposit into Morpho-listed earn vault (V1 or V2)", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1907
|
+
{ id: "morpho.vault-withdraw", protocolId: MORPHO_PROTOCOL_ID, chainCategory: "evm", description: "Withdraw from Morpho earn vault (V1 or V2)", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1908
|
+
{ id: "morpho.blue-collateral-deposit", protocolId: MORPHO_PROTOCOL_ID, chainCategory: "evm", description: "Supply collateral to Morpho Blue market", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1909
|
+
{ id: "morpho.blue-borrow", protocolId: MORPHO_PROTOCOL_ID, chainCategory: "evm", description: "Borrow from Morpho Blue market", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1910
|
+
{ id: "morpho.blue-repay", protocolId: MORPHO_PROTOCOL_ID, chainCategory: "evm", description: "Repay Morpho Blue borrow", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1911
|
+
{ id: "morpho.blue-collateral-withdraw", protocolId: MORPHO_PROTOCOL_ID, chainCategory: "evm", description: "Withdraw Morpho Blue collateral", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1912
|
+
{ id: "morpho.merkl-claim", protocolId: MORPHO_PROTOCOL_ID, chainCategory: "evm", description: "Claim Morpho Merkl rewards", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1913
|
+
{ id: "morpho.midnight-borrow", protocolId: MORPHO_PROTOCOL_ID, chainCategory: "evm", description: "Morpho Midnight fixed-rate borrow (coming soon)", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} }
|
|
1914
|
+
]
|
|
1915
|
+
};
|
|
1916
|
+
registerProtocolModule(morphoProtocolModule);
|
|
1917
|
+
|
|
1918
|
+
exports.MORPHO_BLUE_BORROW_FALLBACK_GAS = MORPHO_BLUE_BORROW_FALLBACK_GAS;
|
|
1919
|
+
exports.MORPHO_BLUE_COLLATERAL_DEPOSIT_FALLBACK_GAS = MORPHO_BLUE_COLLATERAL_DEPOSIT_FALLBACK_GAS;
|
|
1920
|
+
exports.MORPHO_BLUE_COLLATERAL_WITHDRAW_FALLBACK_GAS = MORPHO_BLUE_COLLATERAL_WITHDRAW_FALLBACK_GAS;
|
|
1921
|
+
exports.MORPHO_BLUE_REPAY_FALLBACK_GAS = MORPHO_BLUE_REPAY_FALLBACK_GAS;
|
|
1922
|
+
exports.MORPHO_GRAPHQL_URL = MORPHO_GRAPHQL_URL;
|
|
1923
|
+
exports.MORPHO_MIDNIGHT_UNAVAILABLE_MESSAGE = MORPHO_MIDNIGHT_UNAVAILABLE_MESSAGE;
|
|
1924
|
+
exports.MORPHO_PROTOCOL_ID = MORPHO_PROTOCOL_ID;
|
|
1925
|
+
exports.MORPHO_VAULT_DEPOSIT_FALLBACK_GAS = MORPHO_VAULT_DEPOSIT_FALLBACK_GAS;
|
|
1926
|
+
exports.MORPHO_VAULT_WITHDRAW_FALLBACK_GAS = MORPHO_VAULT_WITHDRAW_FALLBACK_GAS;
|
|
1927
|
+
exports.buildEvmMultisignBodyMorphoBlueBorrowBatch = buildEvmMultisignBodyMorphoBlueBorrowBatch;
|
|
1928
|
+
exports.buildEvmMultisignBodyMorphoBlueRepayBatch = buildEvmMultisignBodyMorphoBlueRepayBatch;
|
|
1929
|
+
exports.buildEvmMultisignBodyMorphoBlueSupplyCollateralBatch = buildEvmMultisignBodyMorphoBlueSupplyCollateralBatch;
|
|
1930
|
+
exports.buildEvmMultisignBodyMorphoBlueWithdrawCollateralBatch = buildEvmMultisignBodyMorphoBlueWithdrawCollateralBatch;
|
|
1931
|
+
exports.buildEvmMultisignBodyMorphoMerklDistributorClaim = buildEvmMultisignBodyMorphoMerklDistributorClaim;
|
|
1932
|
+
exports.buildEvmMultisignBodyMorphoVaultDepositBatch = buildEvmMultisignBodyMorphoVaultDepositBatch;
|
|
1933
|
+
exports.buildEvmMultisignBodyMorphoVaultWithdraw = buildEvmMultisignBodyMorphoVaultWithdraw;
|
|
1934
|
+
exports.emptyMorphoEarnVaultDetailFields = emptyMorphoEarnVaultDetailFields;
|
|
1935
|
+
exports.enrichMorphoEarnOfferingRows = enrichMorphoEarnOfferingRows;
|
|
1936
|
+
exports.enrichMorphoEarnOfferingRowsForAgent = enrichMorphoEarnOfferingRowsForAgent;
|
|
1937
|
+
exports.ensureMorphoChainAssetCache = ensureMorphoChainAssetCache;
|
|
1938
|
+
exports.fetchMorphoBorrowMarketsForCollateral = fetchMorphoBorrowMarketsForCollateral;
|
|
1939
|
+
exports.fetchMorphoBorrowMarketsForLoan = fetchMorphoBorrowMarketsForLoan;
|
|
1940
|
+
exports.fetchMorphoChains = fetchMorphoChains;
|
|
1941
|
+
exports.fetchMorphoEarnOfferingsForAsset = fetchMorphoEarnOfferingsForAsset;
|
|
1942
|
+
exports.fetchMorphoEarnVaultDetails = fetchMorphoEarnVaultDetails;
|
|
1943
|
+
exports.fetchMorphoMarketById = fetchMorphoMarketById;
|
|
1944
|
+
exports.fetchMorphoMarketsForChain = fetchMorphoMarketsForChain;
|
|
1945
|
+
exports.fetchMorphoMidnightIntents = fetchMorphoMidnightIntents;
|
|
1946
|
+
exports.fetchMorphoUserBluePositions = fetchMorphoUserBluePositions;
|
|
1947
|
+
exports.fetchMorphoUserPortfolioUsd = fetchMorphoUserPortfolioUsd;
|
|
1948
|
+
exports.fetchMorphoUserVaultPositions = fetchMorphoUserVaultPositions;
|
|
1949
|
+
exports.fetchMorphoVaultAssetDecimals = fetchMorphoVaultAssetDecimals;
|
|
1950
|
+
exports.fetchMorphoVaultByAddress = fetchMorphoVaultByAddress;
|
|
1951
|
+
exports.fetchMorphoVaultMaxWithdrawWei = fetchMorphoVaultMaxWithdrawWei;
|
|
1952
|
+
exports.fetchMorphoVaultV2sForChain = fetchMorphoVaultV2sForChain;
|
|
1953
|
+
exports.fetchMorphoVaultsForChain = fetchMorphoVaultsForChain;
|
|
1954
|
+
exports.formatMorphoApyPct = formatMorphoApyPct;
|
|
1955
|
+
exports.formatMorphoFeePct = formatMorphoFeePct;
|
|
1956
|
+
exports.formatMorphoMarketApySummary = formatMorphoMarketApySummary;
|
|
1957
|
+
exports.formatMorphoUsd = formatMorphoUsd;
|
|
1958
|
+
exports.isMorphoVaultListed = isMorphoVaultListed;
|
|
1959
|
+
exports.loadMorphoSupportedChainIds = loadMorphoSupportedChainIds;
|
|
1960
|
+
exports.marketParamsFromApiRow = marketParamsFromApiRow;
|
|
1961
|
+
exports.morphoEarnOfferingToDiscoveryRow = morphoEarnOfferingToDiscoveryRow;
|
|
1962
|
+
exports.morphoFetchEarnVaultsSummary = morphoFetchEarnVaultsSummary;
|
|
1963
|
+
exports.morphoGql = morphoGql;
|
|
1964
|
+
exports.morphoKeyForAssetRow = morphoKeyForAssetRow;
|
|
1965
|
+
exports.morphoMarketToBorrowRow = morphoMarketToBorrowRow;
|
|
1966
|
+
exports.morphoProtocolModule = morphoProtocolModule;
|
|
1967
|
+
exports.morphoResolveListedEarnVaultByAddress = morphoResolveListedEarnVaultByAddress;
|
|
1968
|
+
exports.previewMorphoBlueHealthAfterCollateralWithdraw = previewMorphoBlueHealthAfterCollateralWithdraw;
|
|
1969
|
+
exports.searchMorphoListedEarnVaults = searchMorphoListedEarnVaults;
|
|
1970
|
+
//# sourceMappingURL=index.cjs.map
|
|
1971
|
+
//# sourceMappingURL=index.cjs.map
|