@lit-protocol/vincent-ability-morpho 0.0.7-mma
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/CONTRIBUTING.md +90 -0
- package/README.md +136 -0
- package/dist/CONTRIBUTING.md +90 -0
- package/dist/README.md +136 -0
- package/dist/package.json +34 -0
- package/dist/src/generated/lit-action.js +9 -0
- package/dist/src/generated/vincent-ability-metadata.json +3 -0
- package/dist/src/generated/vincent-bundled-ability.d.ts +98 -0
- package/dist/src/generated/vincent-bundled-ability.d.ts.map +1 -0
- package/dist/src/generated/vincent-bundled-ability.js +15 -0
- package/dist/src/generated/vincent-bundled-ability.js.map +1 -0
- package/dist/src/generated/vincent-bundled-ability.ts +13 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +8 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/helpers/index.d.ts +561 -0
- package/dist/src/lib/helpers/index.d.ts.map +1 -0
- package/dist/src/lib/helpers/index.js +1123 -0
- package/dist/src/lib/helpers/index.js.map +1 -0
- package/dist/src/lib/lit-action.d.ts +2 -0
- package/dist/src/lib/lit-action.d.ts.map +1 -0
- package/dist/src/lib/lit-action.js +16 -0
- package/dist/src/lib/lit-action.js.map +1 -0
- package/dist/src/lib/schemas.d.ts +125 -0
- package/dist/src/lib/schemas.d.ts.map +1 -0
- package/dist/src/lib/schemas.js +109 -0
- package/dist/src/lib/schemas.js.map +1 -0
- package/dist/src/lib/vincent-ability.d.ts +96 -0
- package/dist/src/lib/vincent-ability.d.ts.map +1 -0
- package/dist/src/lib/vincent-ability.js +378 -0
- package/dist/src/lib/vincent-ability.js.map +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,1123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.morphoVaultClient = exports.MorphoVaultClient = exports.VAULT_FILTER_PRESETS = exports.ERC20_ABI = exports.MORPHO_MARKET_ABI = exports.ERC4626_VAULT_ABI = exports.CHAIN_IDS = exports.SUPPORTED_CHAINS = exports.WELL_KNOWN_TOKENS = void 0;
|
|
4
|
+
exports.getTokenAddresses = getTokenAddresses;
|
|
5
|
+
exports.getTokenAddress = getTokenAddress;
|
|
6
|
+
exports.isSupportedChain = isSupportedChain;
|
|
7
|
+
exports.getSupportedChainIds = getSupportedChainIds;
|
|
8
|
+
exports.getChainName = getChainName;
|
|
9
|
+
exports.isValidAddress = isValidAddress;
|
|
10
|
+
exports.parseAmount = parseAmount;
|
|
11
|
+
exports.formatAmount = formatAmount;
|
|
12
|
+
exports.validateOperationRequirements = validateOperationRequirements;
|
|
13
|
+
exports.getBestVaultsForAsset = getBestVaultsForAsset;
|
|
14
|
+
exports.getTopVaultsByNetApy = getTopVaultsByNetApy;
|
|
15
|
+
exports.getTopVaultsByTvl = getTopVaultsByTvl;
|
|
16
|
+
exports.searchVaults = searchVaults;
|
|
17
|
+
exports.getVaultsByPreset = getVaultsByPreset;
|
|
18
|
+
exports.getVaults = getVaults;
|
|
19
|
+
exports.getSupportedChainsWithVaults = getSupportedChainsWithVaults;
|
|
20
|
+
exports.getVaultDiscoverySummary = getVaultDiscoverySummary;
|
|
21
|
+
exports.executeMorphoMarketOperation = executeMorphoMarketOperation;
|
|
22
|
+
exports.executeMorphoVaultOperation = executeMorphoVaultOperation;
|
|
23
|
+
const vincent_scaffold_sdk_1 = require("@lit-protocol/vincent-scaffold-sdk");
|
|
24
|
+
const ethers_1 = require("ethers");
|
|
25
|
+
/**
|
|
26
|
+
* Well-known token addresses across different chains
|
|
27
|
+
* Using official Circle USDC and canonical WETH addresses
|
|
28
|
+
*/
|
|
29
|
+
exports.WELL_KNOWN_TOKENS = {
|
|
30
|
+
// Ethereum mainnet
|
|
31
|
+
1: {
|
|
32
|
+
USDC: '0xA0b86991c6218A36c1D19D4a2e9Eb0cE3606eB48', // Circle USDC
|
|
33
|
+
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // Canonical WETH
|
|
34
|
+
USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // Tether USDT
|
|
35
|
+
},
|
|
36
|
+
// Base
|
|
37
|
+
8453: {
|
|
38
|
+
USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // Native USDC on Base
|
|
39
|
+
WETH: '0x4200000000000000000000000000000000000006', // WETH on Base
|
|
40
|
+
USDT: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2', // USDT on Base
|
|
41
|
+
},
|
|
42
|
+
// Arbitrum One
|
|
43
|
+
42161: {
|
|
44
|
+
USDC: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // Native USDC on Arbitrum
|
|
45
|
+
WETH: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', // WETH on Arbitrum
|
|
46
|
+
USDT: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', // USDT on Arbitrum
|
|
47
|
+
},
|
|
48
|
+
// Optimism
|
|
49
|
+
10: {
|
|
50
|
+
USDC: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', // Native USDC on Optimism
|
|
51
|
+
WETH: '0x4200000000000000000000000000000000000006', // WETH on Optimism
|
|
52
|
+
USDT: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', // USDT on Optimism
|
|
53
|
+
},
|
|
54
|
+
// Polygon
|
|
55
|
+
137: {
|
|
56
|
+
USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', // Native USDC on Polygon
|
|
57
|
+
WETH: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619', // WETH on Polygon
|
|
58
|
+
USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', // USDT on Polygon
|
|
59
|
+
},
|
|
60
|
+
// Sepolia testnet
|
|
61
|
+
11155111: {
|
|
62
|
+
USDC: '0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8', // Test USDC on Sepolia
|
|
63
|
+
WETH: '0xC558DBdd856501FCd9aaF1E62eae57A9F0629a3c', // Test WETH on Sepolia
|
|
64
|
+
USDT: '0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0', // Test USDT on Sepolia
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Supported chain IDs and their names
|
|
69
|
+
*/
|
|
70
|
+
exports.SUPPORTED_CHAINS = {
|
|
71
|
+
1: 'ethereum',
|
|
72
|
+
8453: 'base',
|
|
73
|
+
42161: 'arbitrum',
|
|
74
|
+
10: 'optimism',
|
|
75
|
+
137: 'polygon',
|
|
76
|
+
11155111: 'sepolia',
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Chain names to IDs mapping for backwards compatibility
|
|
80
|
+
*/
|
|
81
|
+
exports.CHAIN_IDS = {
|
|
82
|
+
ethereum: 1,
|
|
83
|
+
base: 8453,
|
|
84
|
+
arbitrum: 42161,
|
|
85
|
+
optimism: 10,
|
|
86
|
+
polygon: 137,
|
|
87
|
+
sepolia: 11155111,
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* ERC4626 Vault ABI - Essential methods for Morpho vaults
|
|
91
|
+
*/
|
|
92
|
+
exports.ERC4626_VAULT_ABI = [
|
|
93
|
+
// Deposit
|
|
94
|
+
{
|
|
95
|
+
inputs: [
|
|
96
|
+
{ internalType: 'uint256', name: 'assets', type: 'uint256' },
|
|
97
|
+
{ internalType: 'address', name: 'receiver', type: 'address' },
|
|
98
|
+
],
|
|
99
|
+
name: 'deposit',
|
|
100
|
+
outputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }],
|
|
101
|
+
stateMutability: 'nonpayable',
|
|
102
|
+
type: 'function',
|
|
103
|
+
},
|
|
104
|
+
// Withdraw
|
|
105
|
+
{
|
|
106
|
+
inputs: [
|
|
107
|
+
{ internalType: 'uint256', name: 'assets', type: 'uint256' },
|
|
108
|
+
{ internalType: 'address', name: 'receiver', type: 'address' },
|
|
109
|
+
{ internalType: 'address', name: 'owner', type: 'address' },
|
|
110
|
+
],
|
|
111
|
+
name: 'withdraw',
|
|
112
|
+
outputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }],
|
|
113
|
+
stateMutability: 'nonpayable',
|
|
114
|
+
type: 'function',
|
|
115
|
+
},
|
|
116
|
+
// Redeem
|
|
117
|
+
{
|
|
118
|
+
inputs: [
|
|
119
|
+
{ internalType: 'uint256', name: 'shares', type: 'uint256' },
|
|
120
|
+
{ internalType: 'address', name: 'receiver', type: 'address' },
|
|
121
|
+
{ internalType: 'address', name: 'owner', type: 'address' },
|
|
122
|
+
],
|
|
123
|
+
name: 'redeem',
|
|
124
|
+
outputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }],
|
|
125
|
+
stateMutability: 'nonpayable',
|
|
126
|
+
type: 'function',
|
|
127
|
+
},
|
|
128
|
+
// Asset (underlying token address)
|
|
129
|
+
{
|
|
130
|
+
inputs: [],
|
|
131
|
+
name: 'asset',
|
|
132
|
+
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
|
133
|
+
stateMutability: 'view',
|
|
134
|
+
type: 'function',
|
|
135
|
+
},
|
|
136
|
+
// Balance of shares
|
|
137
|
+
{
|
|
138
|
+
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
|
|
139
|
+
name: 'balanceOf',
|
|
140
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
141
|
+
stateMutability: 'view',
|
|
142
|
+
type: 'function',
|
|
143
|
+
},
|
|
144
|
+
// Convert assets to shares
|
|
145
|
+
{
|
|
146
|
+
inputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }],
|
|
147
|
+
name: 'convertToShares',
|
|
148
|
+
outputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }],
|
|
149
|
+
stateMutability: 'view',
|
|
150
|
+
type: 'function',
|
|
151
|
+
},
|
|
152
|
+
// Convert shares to assets
|
|
153
|
+
{
|
|
154
|
+
inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }],
|
|
155
|
+
name: 'convertToAssets',
|
|
156
|
+
outputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }],
|
|
157
|
+
stateMutability: 'view',
|
|
158
|
+
type: 'function',
|
|
159
|
+
},
|
|
160
|
+
// Total assets managed by the vault
|
|
161
|
+
{
|
|
162
|
+
inputs: [],
|
|
163
|
+
name: 'totalAssets',
|
|
164
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
165
|
+
stateMutability: 'view',
|
|
166
|
+
type: 'function',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
inputs: [],
|
|
170
|
+
name: 'decimals',
|
|
171
|
+
outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
|
|
172
|
+
stateMutability: 'view',
|
|
173
|
+
type: 'function',
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
/**
|
|
177
|
+
* Morpho Market ABI - Essential methods for supply and withdraw operations
|
|
178
|
+
*/
|
|
179
|
+
exports.MORPHO_MARKET_ABI = [
|
|
180
|
+
// Supply
|
|
181
|
+
{
|
|
182
|
+
inputs: [
|
|
183
|
+
{
|
|
184
|
+
components: [
|
|
185
|
+
{ internalType: 'address', name: 'loanToken', type: 'address' },
|
|
186
|
+
{ internalType: 'address', name: 'collateralToken', type: 'address' },
|
|
187
|
+
{ internalType: 'address', name: 'oracle', type: 'address' },
|
|
188
|
+
{ internalType: 'address', name: 'irm', type: 'address' },
|
|
189
|
+
{ internalType: 'uint256', name: 'lltv', type: 'uint256' },
|
|
190
|
+
],
|
|
191
|
+
internalType: 'struct MarketParams',
|
|
192
|
+
name: 'marketParams',
|
|
193
|
+
type: 'tuple',
|
|
194
|
+
},
|
|
195
|
+
{ internalType: 'uint256', name: 'assets', type: 'uint256' },
|
|
196
|
+
{ internalType: 'uint256', name: 'shares', type: 'uint256' },
|
|
197
|
+
{ internalType: 'address', name: 'onBehalf', type: 'address' },
|
|
198
|
+
{ internalType: 'bytes', name: 'data', type: 'bytes' },
|
|
199
|
+
],
|
|
200
|
+
name: 'supply',
|
|
201
|
+
outputs: [
|
|
202
|
+
{ internalType: 'uint256', name: 'assetsSupplied', type: 'uint256' },
|
|
203
|
+
{ internalType: 'uint256', name: 'sharesSupplied', type: 'uint256' },
|
|
204
|
+
],
|
|
205
|
+
stateMutability: 'nonpayable',
|
|
206
|
+
type: 'function',
|
|
207
|
+
},
|
|
208
|
+
// Withdraw Collateral
|
|
209
|
+
{
|
|
210
|
+
inputs: [
|
|
211
|
+
{
|
|
212
|
+
components: [
|
|
213
|
+
{ internalType: 'address', name: 'loanToken', type: 'address' },
|
|
214
|
+
{ internalType: 'address', name: 'collateralToken', type: 'address' },
|
|
215
|
+
{ internalType: 'address', name: 'oracle', type: 'address' },
|
|
216
|
+
{ internalType: 'address', name: 'irm', type: 'address' },
|
|
217
|
+
{ internalType: 'uint256', name: 'lltv', type: 'uint256' },
|
|
218
|
+
],
|
|
219
|
+
internalType: 'struct MarketParams',
|
|
220
|
+
name: 'marketParams',
|
|
221
|
+
type: 'tuple',
|
|
222
|
+
},
|
|
223
|
+
{ internalType: 'uint256', name: 'assets', type: 'uint256' },
|
|
224
|
+
{ internalType: 'address', name: 'onBehalf', type: 'address' },
|
|
225
|
+
{ internalType: 'address', name: 'receiver', type: 'address' },
|
|
226
|
+
],
|
|
227
|
+
name: 'withdrawCollateral',
|
|
228
|
+
outputs: [],
|
|
229
|
+
stateMutability: 'nonpayable',
|
|
230
|
+
type: 'function',
|
|
231
|
+
},
|
|
232
|
+
// Position (to check collateral balance)
|
|
233
|
+
{
|
|
234
|
+
inputs: [
|
|
235
|
+
{ internalType: 'bytes32', name: 'id', type: 'bytes32' },
|
|
236
|
+
{ internalType: 'address', name: 'user', type: 'address' },
|
|
237
|
+
],
|
|
238
|
+
name: 'position',
|
|
239
|
+
outputs: [
|
|
240
|
+
{ internalType: 'uint256', name: 'supplyShares', type: 'uint256' },
|
|
241
|
+
{ internalType: 'uint128', name: 'borrowShares', type: 'uint128' },
|
|
242
|
+
{ internalType: 'uint128', name: 'collateral', type: 'uint128' },
|
|
243
|
+
],
|
|
244
|
+
stateMutability: 'view',
|
|
245
|
+
type: 'function',
|
|
246
|
+
},
|
|
247
|
+
// Market (to check market info)
|
|
248
|
+
{
|
|
249
|
+
inputs: [{ internalType: 'bytes32', name: 'id', type: 'bytes32' }],
|
|
250
|
+
name: 'market',
|
|
251
|
+
outputs: [
|
|
252
|
+
{ internalType: 'uint128', name: 'totalSupplyAssets', type: 'uint128' },
|
|
253
|
+
{ internalType: 'uint128', name: 'totalSupplyShares', type: 'uint128' },
|
|
254
|
+
{ internalType: 'uint128', name: 'totalBorrowAssets', type: 'uint128' },
|
|
255
|
+
{ internalType: 'uint128', name: 'totalBorrowShares', type: 'uint128' },
|
|
256
|
+
{ internalType: 'uint128', name: 'lastUpdate', type: 'uint128' },
|
|
257
|
+
{ internalType: 'uint128', name: 'fee', type: 'uint128' },
|
|
258
|
+
],
|
|
259
|
+
stateMutability: 'view',
|
|
260
|
+
type: 'function',
|
|
261
|
+
},
|
|
262
|
+
// IdToMarketParams (to get market params from ID)
|
|
263
|
+
{
|
|
264
|
+
inputs: [{ internalType: 'bytes32', name: 'id', type: 'bytes32' }],
|
|
265
|
+
name: 'idToMarketParams',
|
|
266
|
+
outputs: [
|
|
267
|
+
{ internalType: 'address', name: 'loanToken', type: 'address' },
|
|
268
|
+
{ internalType: 'address', name: 'collateralToken', type: 'address' },
|
|
269
|
+
{ internalType: 'address', name: 'oracle', type: 'address' },
|
|
270
|
+
{ internalType: 'address', name: 'irm', type: 'address' },
|
|
271
|
+
{ internalType: 'uint256', name: 'lltv', type: 'uint256' },
|
|
272
|
+
],
|
|
273
|
+
stateMutability: 'view',
|
|
274
|
+
type: 'function',
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
/**
|
|
278
|
+
* ERC20 Token ABI - Essential methods only
|
|
279
|
+
*/
|
|
280
|
+
exports.ERC20_ABI = [
|
|
281
|
+
{
|
|
282
|
+
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
|
|
283
|
+
name: 'balanceOf',
|
|
284
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
285
|
+
stateMutability: 'view',
|
|
286
|
+
type: 'function',
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
inputs: [
|
|
290
|
+
{ internalType: 'address', name: 'owner', type: 'address' },
|
|
291
|
+
{ internalType: 'address', name: 'spender', type: 'address' },
|
|
292
|
+
],
|
|
293
|
+
name: 'allowance',
|
|
294
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
295
|
+
stateMutability: 'view',
|
|
296
|
+
type: 'function',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
inputs: [
|
|
300
|
+
{ internalType: 'address', name: 'spender', type: 'address' },
|
|
301
|
+
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
|
|
302
|
+
],
|
|
303
|
+
name: 'approve',
|
|
304
|
+
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
|
305
|
+
stateMutability: 'nonpayable',
|
|
306
|
+
type: 'function',
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
inputs: [],
|
|
310
|
+
name: 'decimals',
|
|
311
|
+
outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
|
|
312
|
+
stateMutability: 'view',
|
|
313
|
+
type: 'function',
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
/**
|
|
317
|
+
* Pre-configured filter presets for common use cases
|
|
318
|
+
*/
|
|
319
|
+
exports.VAULT_FILTER_PRESETS = {
|
|
320
|
+
highYield: {
|
|
321
|
+
minNetApy: 0.08,
|
|
322
|
+
minTvl: 1000000,
|
|
323
|
+
sortBy: 'netApy',
|
|
324
|
+
sortOrder: 'desc',
|
|
325
|
+
excludeIdle: true,
|
|
326
|
+
limit: 10,
|
|
327
|
+
},
|
|
328
|
+
stable: {
|
|
329
|
+
minTvl: 5000000,
|
|
330
|
+
maxNetApy: 0.15,
|
|
331
|
+
whitelistedOnly: true,
|
|
332
|
+
sortBy: 'totalAssetsUsd',
|
|
333
|
+
sortOrder: 'desc',
|
|
334
|
+
excludeIdle: true,
|
|
335
|
+
limit: 10,
|
|
336
|
+
},
|
|
337
|
+
highTvl: {
|
|
338
|
+
minTvl: 10000000,
|
|
339
|
+
sortBy: 'totalAssetsUsd',
|
|
340
|
+
sortOrder: 'desc',
|
|
341
|
+
excludeIdle: true,
|
|
342
|
+
limit: 20,
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* Get well-known token addresses for a specific chain
|
|
347
|
+
*/
|
|
348
|
+
function getTokenAddresses(chainId) {
|
|
349
|
+
if (!(chainId in exports.WELL_KNOWN_TOKENS)) {
|
|
350
|
+
throw new Error(`Unsupported chain ID: ${chainId}. Supported chains: ${Object.keys(exports.WELL_KNOWN_TOKENS).join(', ')}`);
|
|
351
|
+
}
|
|
352
|
+
return exports.WELL_KNOWN_TOKENS[chainId];
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Get token address for a specific token symbol and chain
|
|
356
|
+
*/
|
|
357
|
+
function getTokenAddress(symbol, chainId) {
|
|
358
|
+
const tokens = getTokenAddresses(chainId);
|
|
359
|
+
const upperSymbol = symbol.toUpperCase();
|
|
360
|
+
if (!(upperSymbol in tokens)) {
|
|
361
|
+
throw new Error(`Token ${symbol} not found on chain ${chainId}. Available tokens: ${Object.keys(tokens).join(', ')}`);
|
|
362
|
+
}
|
|
363
|
+
return tokens[upperSymbol];
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Check if a chain is supported by Morpho
|
|
367
|
+
*/
|
|
368
|
+
function isSupportedChain(chainId) {
|
|
369
|
+
return chainId in exports.WELL_KNOWN_TOKENS;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Get all supported chain IDs
|
|
373
|
+
*/
|
|
374
|
+
function getSupportedChainIds() {
|
|
375
|
+
return Object.keys(exports.WELL_KNOWN_TOKENS).map(Number);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Get chain name from chain ID
|
|
379
|
+
*/
|
|
380
|
+
function getChainName(chainId) {
|
|
381
|
+
return exports.SUPPORTED_CHAINS[chainId] || `chain-${chainId}`;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Utility function to validate Ethereum address
|
|
385
|
+
*/
|
|
386
|
+
function isValidAddress(address) {
|
|
387
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Utility function to parse amount with decimals
|
|
391
|
+
*/
|
|
392
|
+
function parseAmount(amount, decimals = 18) {
|
|
393
|
+
return ethers_1.ethers.utils.parseUnits(amount, decimals).toString();
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Utility function to format amount from wei
|
|
397
|
+
*/
|
|
398
|
+
function formatAmount(amount, decimals = 18) {
|
|
399
|
+
return ethers_1.ethers.utils.formatUnits(amount, decimals);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Validate operation-specific requirements for Morpho vaults and markets
|
|
403
|
+
*/
|
|
404
|
+
async function validateOperationRequirements(operation, userBalance, allowance, vaultShares, convertedAmount, collateralBalance) {
|
|
405
|
+
const userBalanceBN = BigInt(userBalance);
|
|
406
|
+
const allowanceBN = BigInt(allowance);
|
|
407
|
+
const vaultSharesBN = BigInt(vaultShares);
|
|
408
|
+
const convertedAmountBN = BigInt(convertedAmount);
|
|
409
|
+
const collateralBalanceBN = collateralBalance ? BigInt(collateralBalance) : 0n;
|
|
410
|
+
switch (operation) {
|
|
411
|
+
case 'vault_deposit':
|
|
412
|
+
// Check if user has enough balance
|
|
413
|
+
if (userBalanceBN < convertedAmountBN) {
|
|
414
|
+
return {
|
|
415
|
+
valid: false,
|
|
416
|
+
error: `Insufficient balance for ${operation} operation. You have ${userBalance} and need ${convertedAmount}`,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
// Check if user has approved vault to spend tokens
|
|
420
|
+
if (allowanceBN < convertedAmountBN) {
|
|
421
|
+
return {
|
|
422
|
+
valid: false,
|
|
423
|
+
error: `Insufficient allowance for ${operation} operation. Please approve vault to spend your tokens first. You have ${allowance} and need ${convertedAmount}`,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
break;
|
|
427
|
+
case 'vault_withdraw':
|
|
428
|
+
// For withdraw, we need to check if user has enough vault shares
|
|
429
|
+
if (vaultSharesBN === 0n) {
|
|
430
|
+
return {
|
|
431
|
+
valid: false,
|
|
432
|
+
error: 'No vault shares available for withdrawal',
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
// Note: We'll need to convert the amount to shares in the actual implementation
|
|
436
|
+
break;
|
|
437
|
+
case 'vault_redeem':
|
|
438
|
+
// For redeem, we need to check if user has enough vault shares
|
|
439
|
+
if (vaultSharesBN === 0n) {
|
|
440
|
+
return {
|
|
441
|
+
valid: false,
|
|
442
|
+
error: 'No vault shares available for redeem',
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
// For redeem, the amount is in shares, so check directly
|
|
446
|
+
if (vaultSharesBN < convertedAmountBN) {
|
|
447
|
+
return {
|
|
448
|
+
valid: false,
|
|
449
|
+
error: `Insufficient vault shares for redeem operation. You have ${vaultShares} shares and need ${convertedAmount} shares`,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
case 'market_supply':
|
|
454
|
+
// Check if user has enough balance
|
|
455
|
+
if (userBalanceBN < convertedAmountBN) {
|
|
456
|
+
return {
|
|
457
|
+
valid: false,
|
|
458
|
+
error: `Insufficient balance for market supply operation. You have ${userBalance} and need ${convertedAmount}`,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
// Check if user has approved market to spend tokens
|
|
462
|
+
if (allowanceBN < convertedAmountBN) {
|
|
463
|
+
return {
|
|
464
|
+
valid: false,
|
|
465
|
+
error: `Insufficient allowance for market supply operation. Please approve market to spend your tokens first. You have ${allowance} and need ${convertedAmount}`,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
break;
|
|
469
|
+
case 'market_withdrawCollateral':
|
|
470
|
+
// Check if user has enough collateral balance
|
|
471
|
+
if (collateralBalanceBN < convertedAmountBN) {
|
|
472
|
+
return {
|
|
473
|
+
valid: false,
|
|
474
|
+
error: `Insufficient collateral balance for withdrawal. You have ${collateralBalance} and need ${convertedAmount}`,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
break;
|
|
478
|
+
default:
|
|
479
|
+
return { valid: false, error: `Unsupported operation: ${operation}` };
|
|
480
|
+
}
|
|
481
|
+
return { valid: true };
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Morpho GraphQL API Client
|
|
485
|
+
*/
|
|
486
|
+
class MorphoVaultClient {
|
|
487
|
+
apiUrl = 'https://blue-api.morpho.org/graphql';
|
|
488
|
+
/**
|
|
489
|
+
* Fetch vault data from Morpho GraphQL API
|
|
490
|
+
*/
|
|
491
|
+
async fetchVaultData(query, variables) {
|
|
492
|
+
try {
|
|
493
|
+
// console.log("fetchVaultData", query, variables);
|
|
494
|
+
const response = await fetch(this.apiUrl, {
|
|
495
|
+
method: 'POST',
|
|
496
|
+
headers: {
|
|
497
|
+
'Content-Type': 'application/json',
|
|
498
|
+
},
|
|
499
|
+
body: JSON.stringify({
|
|
500
|
+
query,
|
|
501
|
+
variables,
|
|
502
|
+
}),
|
|
503
|
+
});
|
|
504
|
+
if (!response.ok) {
|
|
505
|
+
const body = await response.text();
|
|
506
|
+
throw new Error(`HTTP error! status: ${response.status} and body: ${body}`);
|
|
507
|
+
}
|
|
508
|
+
const data = await response.json();
|
|
509
|
+
if (data.errors) {
|
|
510
|
+
throw new Error(`GraphQL error: ${data.errors.map((e) => e.message).join(', ')}`);
|
|
511
|
+
}
|
|
512
|
+
return data.data;
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
console.error('Failed to fetch vault data:', error);
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Get all vaults with comprehensive information
|
|
521
|
+
* Now uses proper server-side filtering via GraphQL VaultFilters
|
|
522
|
+
*/
|
|
523
|
+
async getAllVaults(options = {}) {
|
|
524
|
+
// Build GraphQL where clause from options
|
|
525
|
+
const whereClause = this.buildVaultFilters(options);
|
|
526
|
+
const query = `
|
|
527
|
+
query GetAllVaults($first: Int, $orderBy: VaultOrderBy, $orderDirection: OrderDirection, $where: VaultFilters) {
|
|
528
|
+
vaults(first: $first, orderBy: $orderBy, orderDirection: $orderDirection, where: $where) {
|
|
529
|
+
items {
|
|
530
|
+
address
|
|
531
|
+
name
|
|
532
|
+
symbol
|
|
533
|
+
whitelisted
|
|
534
|
+
creationTimestamp
|
|
535
|
+
asset {
|
|
536
|
+
address
|
|
537
|
+
symbol
|
|
538
|
+
name
|
|
539
|
+
decimals
|
|
540
|
+
}
|
|
541
|
+
chain {
|
|
542
|
+
id
|
|
543
|
+
network
|
|
544
|
+
}
|
|
545
|
+
state {
|
|
546
|
+
apy
|
|
547
|
+
netApy
|
|
548
|
+
totalAssets
|
|
549
|
+
totalAssetsUsd
|
|
550
|
+
fee
|
|
551
|
+
rewards {
|
|
552
|
+
asset {
|
|
553
|
+
address
|
|
554
|
+
symbol
|
|
555
|
+
}
|
|
556
|
+
supplyApr
|
|
557
|
+
yearlySupplyTokens
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
`;
|
|
564
|
+
// Fetch more results than requested to account for client-side filtering
|
|
565
|
+
// If excludeIdle is true, we might need to filter some out, so fetch extra
|
|
566
|
+
// But never exceed 1000 (GraphQL API limit)
|
|
567
|
+
const calculateFetchLimit = (requestedLimit) => {
|
|
568
|
+
if (!requestedLimit)
|
|
569
|
+
return 100; // Default limit
|
|
570
|
+
// Always cap at 1000 to respect GraphQL API limits
|
|
571
|
+
const cappedLimit = Math.min(requestedLimit, 1000);
|
|
572
|
+
if (options.excludeIdle) {
|
|
573
|
+
// For excludeIdle filtering, fetch extra but never exceed 1000
|
|
574
|
+
return Math.max(cappedLimit, 1000);
|
|
575
|
+
}
|
|
576
|
+
// No client-side filtering, use requested limit (capped at 1000)
|
|
577
|
+
return cappedLimit;
|
|
578
|
+
};
|
|
579
|
+
const fetchLimit = calculateFetchLimit(options.limit);
|
|
580
|
+
const variables = {
|
|
581
|
+
first: fetchLimit,
|
|
582
|
+
orderBy: this.mapSortBy(options.sortBy || 'totalAssetsUsd'),
|
|
583
|
+
orderDirection: options.sortOrder === 'asc' ? 'Asc' : 'Desc',
|
|
584
|
+
where: whereClause,
|
|
585
|
+
};
|
|
586
|
+
const data = await this.fetchVaultData(query, variables);
|
|
587
|
+
const vaults = data.vaults.items.map((vault) => this.mapVaultData(vault));
|
|
588
|
+
// console.log("vaults after server-side filtering", vaults.length);
|
|
589
|
+
// Apply only remaining client-side filters not supported by GraphQL
|
|
590
|
+
const filtered = this.applyRemainingClientFilters(vaults, options);
|
|
591
|
+
// console.log("vaults after additional client filtering", filtered.length);
|
|
592
|
+
// Apply the limit AFTER client-side filtering to ensure we get the expected number of results
|
|
593
|
+
const finalResults = options.limit ? filtered.slice(0, options.limit) : filtered;
|
|
594
|
+
// Log a warning if we couldn't fetch enough results due to API limits
|
|
595
|
+
if (options.limit && options.limit > 1000 && finalResults.length < options.limit) {
|
|
596
|
+
console.warn(`Warning: Requested ${options.limit} vaults but GraphQL API limit is 1000. Got ${finalResults.length} results.`);
|
|
597
|
+
}
|
|
598
|
+
return finalResults;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Unified function to get vaults with flexible filtering
|
|
602
|
+
* Supports filtering by asset, chain, and all other options
|
|
603
|
+
*/
|
|
604
|
+
async getVaults(options = {}) {
|
|
605
|
+
// If specific asset or chain filters are provided, enhance the options
|
|
606
|
+
const enhancedOptions = { ...options };
|
|
607
|
+
// Handle chain filtering - support both chainId and chain name/ID
|
|
608
|
+
if (options.chainId) {
|
|
609
|
+
enhancedOptions.chain = options.chainId;
|
|
610
|
+
}
|
|
611
|
+
return this.getAllVaults(enhancedOptions);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Get top vaults by APY
|
|
615
|
+
*/
|
|
616
|
+
async getTopVaultsByNetApy(limit = 10, minTvl = 0) {
|
|
617
|
+
return this.getAllVaults({
|
|
618
|
+
sortBy: 'netApy',
|
|
619
|
+
sortOrder: 'desc',
|
|
620
|
+
limit,
|
|
621
|
+
minTvl,
|
|
622
|
+
excludeIdle: true,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Get top vaults by TVL
|
|
627
|
+
*/
|
|
628
|
+
async getTopVaultsByTvl(limit = 10) {
|
|
629
|
+
return this.getAllVaults({
|
|
630
|
+
sortBy: 'totalAssetsUsd',
|
|
631
|
+
sortOrder: 'desc',
|
|
632
|
+
limit,
|
|
633
|
+
excludeIdle: true,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Search vaults by name, symbol, or asset
|
|
638
|
+
*/
|
|
639
|
+
async searchVaults(searchOptions) {
|
|
640
|
+
const allVaults = await this.getAllVaults({ limit: 500 }); // Reduced to avoid GraphQL limit issues
|
|
641
|
+
if (!searchOptions.query) {
|
|
642
|
+
return allVaults.slice(0, searchOptions.limit || 50);
|
|
643
|
+
}
|
|
644
|
+
const query = searchOptions.query.toLowerCase();
|
|
645
|
+
const filtered = allVaults.filter((vault) => vault.name.toLowerCase().includes(query) ||
|
|
646
|
+
vault.symbol.toLowerCase().includes(query) ||
|
|
647
|
+
vault.asset.symbol.toLowerCase().includes(query) ||
|
|
648
|
+
vault.asset.name.toLowerCase().includes(query));
|
|
649
|
+
return filtered.slice(0, searchOptions.limit || 50);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Get vault details by address
|
|
653
|
+
*/
|
|
654
|
+
async getVaultByAddress(address, chainId) {
|
|
655
|
+
const query = `
|
|
656
|
+
query GetVaultByAddress($address: String!, $chainId: Int!) {
|
|
657
|
+
vaultByAddress(address: $address, chainId: $chainId) {
|
|
658
|
+
address
|
|
659
|
+
name
|
|
660
|
+
symbol
|
|
661
|
+
whitelisted
|
|
662
|
+
creationTimestamp
|
|
663
|
+
asset {
|
|
664
|
+
address
|
|
665
|
+
symbol
|
|
666
|
+
name
|
|
667
|
+
decimals
|
|
668
|
+
}
|
|
669
|
+
chain {
|
|
670
|
+
id
|
|
671
|
+
network
|
|
672
|
+
}
|
|
673
|
+
state {
|
|
674
|
+
apy
|
|
675
|
+
netApy
|
|
676
|
+
totalAssets
|
|
677
|
+
totalAssetsUsd
|
|
678
|
+
fee
|
|
679
|
+
rewards {
|
|
680
|
+
asset {
|
|
681
|
+
address
|
|
682
|
+
symbol
|
|
683
|
+
}
|
|
684
|
+
supplyApr
|
|
685
|
+
yearlySupplyTokens
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
`;
|
|
691
|
+
const variables = { address, chainId };
|
|
692
|
+
try {
|
|
693
|
+
const data = await this.fetchVaultData(query, variables);
|
|
694
|
+
return data.vaultByAddress ? this.mapVaultData(data.vaultByAddress) : null;
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
console.error(`Failed to fetch vault ${address}:`, error);
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Get best vaults for a specific asset
|
|
703
|
+
*/
|
|
704
|
+
async getBestVaultsForAsset(assetSymbol, limit = 5) {
|
|
705
|
+
const vaults = await this.getAllVaults({
|
|
706
|
+
sortBy: 'netApy',
|
|
707
|
+
sortOrder: 'desc',
|
|
708
|
+
limit: 100,
|
|
709
|
+
minTvl: 10000, // Minimum $10k TVL
|
|
710
|
+
excludeIdle: true,
|
|
711
|
+
});
|
|
712
|
+
return vaults
|
|
713
|
+
.filter((vault) => vault.asset.symbol.toLowerCase() === assetSymbol.toLowerCase())
|
|
714
|
+
.slice(0, limit);
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Map vault data from GraphQL response
|
|
718
|
+
*/
|
|
719
|
+
mapVaultData(vault) {
|
|
720
|
+
return {
|
|
721
|
+
address: vault.address,
|
|
722
|
+
name: vault.name,
|
|
723
|
+
symbol: vault.symbol,
|
|
724
|
+
asset: {
|
|
725
|
+
address: vault.asset.address,
|
|
726
|
+
symbol: vault.asset.symbol,
|
|
727
|
+
name: vault.asset.name,
|
|
728
|
+
decimals: vault.asset.decimals,
|
|
729
|
+
},
|
|
730
|
+
chain: {
|
|
731
|
+
id: vault.chain.id,
|
|
732
|
+
network: vault.chain.network,
|
|
733
|
+
},
|
|
734
|
+
metrics: {
|
|
735
|
+
apy: vault.state.apy || 0,
|
|
736
|
+
netApy: vault.state.netApy || 0,
|
|
737
|
+
totalAssets: vault.state.totalAssets || '0',
|
|
738
|
+
totalAssetsUsd: vault.state.totalAssetsUsd || 0,
|
|
739
|
+
fee: vault.state.fee || 0,
|
|
740
|
+
rewards: vault.state.rewards?.map((reward) => ({
|
|
741
|
+
asset: reward.asset.address,
|
|
742
|
+
supplyApr: reward.supplyApr,
|
|
743
|
+
yearlySupplyTokens: reward.yearlySupplyTokens,
|
|
744
|
+
})) || [],
|
|
745
|
+
},
|
|
746
|
+
whitelisted: vault.whitelisted,
|
|
747
|
+
creationTimestamp: vault.creationTimestamp,
|
|
748
|
+
isIdle: vault.state.totalAssetsUsd < 100, // Consider vaults with < $100 TVL as idle
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Build GraphQL VaultFilters from filter options
|
|
753
|
+
* Uses proper server-side filtering for better performance
|
|
754
|
+
*/
|
|
755
|
+
buildVaultFilters(options) {
|
|
756
|
+
const filters = {};
|
|
757
|
+
// Chain filtering - server-side supported
|
|
758
|
+
if (options.chain !== undefined || options.chainId !== undefined) {
|
|
759
|
+
let targetChainId;
|
|
760
|
+
if (options.chainId !== undefined) {
|
|
761
|
+
targetChainId = options.chainId;
|
|
762
|
+
}
|
|
763
|
+
else if (options.chain !== undefined) {
|
|
764
|
+
targetChainId =
|
|
765
|
+
typeof options.chain === 'string'
|
|
766
|
+
? exports.CHAIN_IDS[options.chain]
|
|
767
|
+
: options.chain;
|
|
768
|
+
}
|
|
769
|
+
if (targetChainId !== undefined) {
|
|
770
|
+
filters.chainId_in = [targetChainId];
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
// Asset filtering - server-side supported
|
|
774
|
+
if (options.assetAddress) {
|
|
775
|
+
filters.assetAddress_in = [options.assetAddress.toLowerCase()];
|
|
776
|
+
}
|
|
777
|
+
if (options.assetSymbol) {
|
|
778
|
+
filters.assetSymbol_in = [options.assetSymbol.toUpperCase()];
|
|
779
|
+
}
|
|
780
|
+
// Whitelisted status filtering - server-side supported
|
|
781
|
+
if (options.whitelistedOnly) {
|
|
782
|
+
filters.whitelisted = true;
|
|
783
|
+
}
|
|
784
|
+
// Net APY filtering - server-side supported
|
|
785
|
+
if (options.minNetApy !== undefined) {
|
|
786
|
+
filters.netApy_gte = options.minNetApy;
|
|
787
|
+
}
|
|
788
|
+
if (options.maxNetApy !== undefined) {
|
|
789
|
+
filters.netApy_lte = options.maxNetApy;
|
|
790
|
+
}
|
|
791
|
+
// TVL filtering - server-side supported
|
|
792
|
+
if (options.minTvl !== undefined) {
|
|
793
|
+
filters.totalAssetsUsd_gte = options.minTvl;
|
|
794
|
+
}
|
|
795
|
+
if (options.maxTvl !== undefined) {
|
|
796
|
+
filters.totalAssetsUsd_lte = options.maxTvl;
|
|
797
|
+
}
|
|
798
|
+
// Total assets filtering - server-side supported
|
|
799
|
+
if (options.minTotalAssets !== undefined) {
|
|
800
|
+
filters.totalAssets_gte = options.minTotalAssets.toString();
|
|
801
|
+
}
|
|
802
|
+
if (options.maxTotalAssets !== undefined) {
|
|
803
|
+
filters.totalAssets_lte = options.maxTotalAssets.toString();
|
|
804
|
+
}
|
|
805
|
+
// Return null if no filters to avoid empty where clause
|
|
806
|
+
return Object.keys(filters).length > 0 ? filters : null;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Apply remaining client-side filters not supported by GraphQL
|
|
810
|
+
* Only handles computed properties like isIdle
|
|
811
|
+
*/
|
|
812
|
+
applyRemainingClientFilters(vaults, options) {
|
|
813
|
+
let filtered = vaults;
|
|
814
|
+
// Idle vault filtering (computed client-side)
|
|
815
|
+
if (options.excludeIdle) {
|
|
816
|
+
filtered = filtered.filter((vault) => !vault.isIdle);
|
|
817
|
+
}
|
|
818
|
+
return filtered;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Map sortBy option to GraphQL enum
|
|
822
|
+
*/
|
|
823
|
+
mapSortBy(sortBy) {
|
|
824
|
+
switch (sortBy) {
|
|
825
|
+
case 'netApy':
|
|
826
|
+
return 'NetApy';
|
|
827
|
+
case 'totalAssets':
|
|
828
|
+
return 'TotalAssets';
|
|
829
|
+
case 'totalAssetsUsd':
|
|
830
|
+
return 'TotalAssetsUsd';
|
|
831
|
+
case 'creationTimestamp':
|
|
832
|
+
return 'CreationTimestamp';
|
|
833
|
+
default:
|
|
834
|
+
return 'TotalAssetsUsd';
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
exports.MorphoVaultClient = MorphoVaultClient;
|
|
839
|
+
/**
|
|
840
|
+
* Create a singleton instance of MorphoVaultClient
|
|
841
|
+
*/
|
|
842
|
+
exports.morphoVaultClient = new MorphoVaultClient();
|
|
843
|
+
/**
|
|
844
|
+
* Helper function to get best vaults for a specific asset
|
|
845
|
+
*/
|
|
846
|
+
async function getBestVaultsForAsset(assetSymbol, limit = 5) {
|
|
847
|
+
return exports.morphoVaultClient.getBestVaultsForAsset(assetSymbol, limit);
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Helper function to get top vaults by APY
|
|
851
|
+
*/
|
|
852
|
+
async function getTopVaultsByNetApy(limit = 10, minTvl = 10000) {
|
|
853
|
+
return exports.morphoVaultClient.getTopVaultsByNetApy(limit, minTvl);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Helper function to get top vaults by TVL
|
|
857
|
+
*/
|
|
858
|
+
async function getTopVaultsByTvl(limit = 10) {
|
|
859
|
+
return exports.morphoVaultClient.getTopVaultsByTvl(limit);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Helper function to search vaults
|
|
863
|
+
*/
|
|
864
|
+
async function searchVaults(query, limit = 20) {
|
|
865
|
+
return exports.morphoVaultClient.searchVaults({ query, limit });
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* 🚀 **Quick Vault Search with Presets**
|
|
869
|
+
*
|
|
870
|
+
* Get vaults using pre-configured filter presets for common use cases.
|
|
871
|
+
*
|
|
872
|
+
* @param preset - Pre-configured filter preset
|
|
873
|
+
* @param overrides - Additional options to override preset defaults
|
|
874
|
+
* @returns Promise resolving to array of vault information
|
|
875
|
+
*
|
|
876
|
+
* @example
|
|
877
|
+
* ```typescript
|
|
878
|
+
* // Find high-yield vaults
|
|
879
|
+
* const highYieldVaults = await getVaultsByPreset("highYield");
|
|
880
|
+
*
|
|
881
|
+
* // Find high-yield USDC vaults specifically
|
|
882
|
+
* const usdcHighYield = await getVaultsByPreset("highYield", {
|
|
883
|
+
* assetSymbol: "USDC"
|
|
884
|
+
* });
|
|
885
|
+
*
|
|
886
|
+
* // Find stable vaults on Base chain
|
|
887
|
+
* const stableBaseVaults = await getVaultsByPreset("stable", {
|
|
888
|
+
* chainId: 8453
|
|
889
|
+
* });
|
|
890
|
+
* ```
|
|
891
|
+
*/
|
|
892
|
+
async function getVaultsByPreset(preset, overrides = {}) {
|
|
893
|
+
const presetOptions = exports.VAULT_FILTER_PRESETS[preset];
|
|
894
|
+
const mergedOptions = { ...presetOptions, ...overrides };
|
|
895
|
+
return getVaults(mergedOptions);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* 🔍 **Primary Vault Discovery Function**
|
|
899
|
+
*
|
|
900
|
+
* Get Morpho vaults with comprehensive filtering and sorting options.
|
|
901
|
+
* Uses server-side GraphQL queries for optimal performance.
|
|
902
|
+
*
|
|
903
|
+
* @param options - Vault filtering and sorting options
|
|
904
|
+
* @returns Promise resolving to array of vault information
|
|
905
|
+
*
|
|
906
|
+
* @example
|
|
907
|
+
* ```typescript
|
|
908
|
+
* // Find best USDC vaults across all chains
|
|
909
|
+
* const topVaults = await getVaults({
|
|
910
|
+
* assetSymbol: "USDC",
|
|
911
|
+
* minNetApy: 0.05,
|
|
912
|
+
* minTvl: 1000000,
|
|
913
|
+
* sortBy: "netApy",
|
|
914
|
+
* sortOrder: "desc",
|
|
915
|
+
* limit: 5
|
|
916
|
+
* });
|
|
917
|
+
*
|
|
918
|
+
* // Filter by specific chain
|
|
919
|
+
* const baseVaults = await getVaults({
|
|
920
|
+
* chainId: 8453, // Base
|
|
921
|
+
* excludeIdle: true,
|
|
922
|
+
* sortBy: "totalAssetsUsd"
|
|
923
|
+
* });
|
|
924
|
+
*
|
|
925
|
+
* // Search with multiple criteria
|
|
926
|
+
* const premiumVaults = await getVaults({
|
|
927
|
+
* minNetApy: 10.0,
|
|
928
|
+
* minTvl: 5000000,
|
|
929
|
+
* whitelistedOnly: true,
|
|
930
|
+
* sortBy: "netApy",
|
|
931
|
+
* limit: 3
|
|
932
|
+
* });
|
|
933
|
+
* ```
|
|
934
|
+
*/
|
|
935
|
+
async function getVaults(options = {}) {
|
|
936
|
+
return exports.morphoVaultClient.getVaults(options);
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Get supported chains with active vaults
|
|
940
|
+
*/
|
|
941
|
+
async function getSupportedChainsWithVaults() {
|
|
942
|
+
const supportedChains = getSupportedChainIds();
|
|
943
|
+
const results = [];
|
|
944
|
+
for (const chainId of supportedChains) {
|
|
945
|
+
try {
|
|
946
|
+
const vaults = await exports.morphoVaultClient.getVaults({
|
|
947
|
+
chainId,
|
|
948
|
+
limit: 1,
|
|
949
|
+
excludeIdle: true,
|
|
950
|
+
});
|
|
951
|
+
if (vaults.length > 0) {
|
|
952
|
+
// Get total count - reduced limit to avoid GraphQL errors
|
|
953
|
+
const allVaults = await exports.morphoVaultClient.getVaults({
|
|
954
|
+
chainId,
|
|
955
|
+
limit: 500, // Reduced to avoid GraphQL limit issues
|
|
956
|
+
excludeIdle: true,
|
|
957
|
+
});
|
|
958
|
+
results.push({
|
|
959
|
+
chainId,
|
|
960
|
+
name: getChainName(chainId),
|
|
961
|
+
vaultCount: allVaults.length,
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
catch (error) {
|
|
966
|
+
console.warn(`Could not fetch vaults for chain ${chainId}:`, error instanceof Error ? error.message : String(error));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return results.sort((a, b) => b.vaultCount - a.vaultCount);
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Get vault discovery summary for a chain
|
|
973
|
+
*/
|
|
974
|
+
async function getVaultDiscoverySummary(chainId) {
|
|
975
|
+
try {
|
|
976
|
+
const [topByTvl, topByNetApy, assetBreakdown] = await Promise.all([
|
|
977
|
+
exports.morphoVaultClient.getVaults({
|
|
978
|
+
chainId,
|
|
979
|
+
sortBy: 'totalAssetsUsd',
|
|
980
|
+
sortOrder: 'desc',
|
|
981
|
+
limit: 5,
|
|
982
|
+
excludeIdle: true,
|
|
983
|
+
}),
|
|
984
|
+
exports.morphoVaultClient.getVaults({
|
|
985
|
+
chainId,
|
|
986
|
+
sortBy: 'netApy',
|
|
987
|
+
sortOrder: 'desc',
|
|
988
|
+
limit: 5,
|
|
989
|
+
excludeIdle: true,
|
|
990
|
+
}),
|
|
991
|
+
exports.morphoVaultClient.getVaults({
|
|
992
|
+
chainId,
|
|
993
|
+
limit: 500, // Reduced to avoid GraphQL limit issues
|
|
994
|
+
excludeIdle: true,
|
|
995
|
+
}),
|
|
996
|
+
]);
|
|
997
|
+
// Group by asset
|
|
998
|
+
const assetGroups = assetBreakdown.reduce((acc, vault) => {
|
|
999
|
+
const symbol = vault.asset.symbol;
|
|
1000
|
+
if (!acc[symbol]) {
|
|
1001
|
+
acc[symbol] = { count: 0, totalTvl: 0, maxNetApy: 0 };
|
|
1002
|
+
}
|
|
1003
|
+
acc[symbol].count++;
|
|
1004
|
+
acc[symbol].totalTvl += vault.metrics.totalAssetsUsd;
|
|
1005
|
+
acc[symbol].maxNetApy = Math.max(acc[symbol].maxNetApy, vault.metrics.netApy);
|
|
1006
|
+
return acc;
|
|
1007
|
+
}, {});
|
|
1008
|
+
return {
|
|
1009
|
+
chainId,
|
|
1010
|
+
chainName: getChainName(chainId),
|
|
1011
|
+
totalVaults: assetBreakdown.length,
|
|
1012
|
+
totalTvl: assetBreakdown.reduce((sum, v) => sum + v.metrics.totalAssetsUsd, 0),
|
|
1013
|
+
topVaultsByTvl: topByTvl,
|
|
1014
|
+
topVaultsByNetApy: topByNetApy,
|
|
1015
|
+
assetBreakdown: Object.entries(assetGroups)
|
|
1016
|
+
.map(([symbol, data]) => ({ symbol, ...data }))
|
|
1017
|
+
.sort((a, b) => b.totalTvl - a.totalTvl),
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
catch (error) {
|
|
1021
|
+
console.error(`Error getting vault summary for chain ${chainId}:`, error);
|
|
1022
|
+
throw error;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Execute Morpho market operations (supply/withdrawCollateral)
|
|
1027
|
+
*/
|
|
1028
|
+
async function executeMorphoMarketOperation({ provider, pkpPublicKey, marketAddress, marketId, functionName, args, chainId, alchemyGasSponsor, alchemyGasSponsorApiKey, alchemyGasSponsorPolicyId, }) {
|
|
1029
|
+
console.log(`[@lit-protocol/vincent-ability-morpho/executeMorphoMarketOperation] Starting ${functionName} operation`, { sponsored: !!alchemyGasSponsor, marketId });
|
|
1030
|
+
// Use gas sponsorship if enabled and all required parameters are provided
|
|
1031
|
+
if (alchemyGasSponsor && alchemyGasSponsorApiKey && alchemyGasSponsorPolicyId) {
|
|
1032
|
+
console.log(`[@lit-protocol/vincent-ability-morpho/executeMorphoMarketOperation] Using EIP-7702 gas sponsorship`, { marketAddress, functionName, args, policyId: alchemyGasSponsorPolicyId });
|
|
1033
|
+
try {
|
|
1034
|
+
return await vincent_scaffold_sdk_1.laUtils.transaction.handler.sponsoredGasContractCall({
|
|
1035
|
+
pkpPublicKey,
|
|
1036
|
+
abi: exports.MORPHO_MARKET_ABI,
|
|
1037
|
+
contractAddress: marketAddress,
|
|
1038
|
+
functionName,
|
|
1039
|
+
args,
|
|
1040
|
+
chainId,
|
|
1041
|
+
eip7702AlchemyApiKey: alchemyGasSponsorApiKey,
|
|
1042
|
+
eip7702AlchemyPolicyId: alchemyGasSponsorPolicyId,
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
catch (error) {
|
|
1046
|
+
console.error(`[@lit-protocol/vincent-ability-morpho/executeMorphoMarketOperation] EIP-7702 operation failed:`, error);
|
|
1047
|
+
throw error;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
else {
|
|
1051
|
+
// Use regular transaction without gas sponsorship
|
|
1052
|
+
console.log(`[@lit-protocol/vincent-ability-morpho/executeMorphoMarketOperation] Using regular transaction`);
|
|
1053
|
+
if (!provider) {
|
|
1054
|
+
throw new Error('Provider is required for non-sponsored transactions');
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
return await vincent_scaffold_sdk_1.laUtils.transaction.handler.contractCall({
|
|
1058
|
+
provider,
|
|
1059
|
+
pkpPublicKey,
|
|
1060
|
+
callerAddress: ethers_1.ethers.utils.computeAddress(pkpPublicKey),
|
|
1061
|
+
abi: exports.MORPHO_MARKET_ABI,
|
|
1062
|
+
contractAddress: marketAddress,
|
|
1063
|
+
functionName,
|
|
1064
|
+
args,
|
|
1065
|
+
chainId,
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
catch (error) {
|
|
1069
|
+
console.error(`[@lit-protocol/vincent-ability-morpho/executeMorphoMarketOperation] Regular transaction failed:`, error);
|
|
1070
|
+
throw error;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Generic function to execute any Morpho Vault operation, with optional gas sponsorship
|
|
1076
|
+
*/
|
|
1077
|
+
async function executeMorphoVaultOperation({ provider, pkpPublicKey, vaultAddress, functionName, args, chainId, alchemyGasSponsor, alchemyGasSponsorApiKey, alchemyGasSponsorPolicyId, }) {
|
|
1078
|
+
console.log(`[@lit-protocol/vincent-ability-morpho/executeMorphoVaultOperation] Starting ${functionName} operation`, { sponsored: !!alchemyGasSponsor });
|
|
1079
|
+
// Use gas sponsorship if enabled and all required parameters are provided
|
|
1080
|
+
if (alchemyGasSponsor && alchemyGasSponsorApiKey && alchemyGasSponsorPolicyId) {
|
|
1081
|
+
console.log(`[@lit-protocol/vincent-ability-morpho/executeMorphoVaultOperation] Using EIP-7702 gas sponsorship`, { vaultAddress, functionName, args, policyId: alchemyGasSponsorPolicyId });
|
|
1082
|
+
try {
|
|
1083
|
+
return await vincent_scaffold_sdk_1.laUtils.transaction.handler.sponsoredGasContractCall({
|
|
1084
|
+
pkpPublicKey,
|
|
1085
|
+
abi: exports.ERC4626_VAULT_ABI,
|
|
1086
|
+
contractAddress: vaultAddress,
|
|
1087
|
+
functionName,
|
|
1088
|
+
args,
|
|
1089
|
+
chainId,
|
|
1090
|
+
eip7702AlchemyApiKey: alchemyGasSponsorApiKey,
|
|
1091
|
+
eip7702AlchemyPolicyId: alchemyGasSponsorPolicyId,
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
catch (error) {
|
|
1095
|
+
console.error(`[@lit-protocol/vincent-ability-morpho/executeMorphoVaultOperation] EIP-7702 operation failed:`, error);
|
|
1096
|
+
throw error;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
// Use regular transaction without gas sponsorship
|
|
1101
|
+
console.log(`[@lit-protocol/vincent-ability-morpho/executeMorphoVaultOperation] Using regular transaction`);
|
|
1102
|
+
if (!provider) {
|
|
1103
|
+
throw new Error('Provider is required for non-sponsored transactions');
|
|
1104
|
+
}
|
|
1105
|
+
try {
|
|
1106
|
+
return await vincent_scaffold_sdk_1.laUtils.transaction.handler.contractCall({
|
|
1107
|
+
provider,
|
|
1108
|
+
pkpPublicKey,
|
|
1109
|
+
callerAddress: ethers_1.ethers.utils.computeAddress(pkpPublicKey),
|
|
1110
|
+
abi: exports.ERC4626_VAULT_ABI,
|
|
1111
|
+
contractAddress: vaultAddress,
|
|
1112
|
+
functionName,
|
|
1113
|
+
args,
|
|
1114
|
+
chainId,
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
catch (error) {
|
|
1118
|
+
console.error(`[@lit-protocol/vincent-ability-morpho/executeMorphoVaultOperation] Regular transaction failed:`, error);
|
|
1119
|
+
throw error;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
//# sourceMappingURL=index.js.map
|