curvance 5.0.0 → 5.1.0
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/README.md +22 -7
- package/dist/abis/OptimizerReader.json +43 -45
- package/dist/abis/ProtocolReader.json +279 -55
- package/dist/chains/arbitrum.js +3 -3
- package/dist/chains/arbitrum.js.map +1 -1
- package/dist/classes/Api.d.ts +2 -2
- package/dist/classes/Api.d.ts.map +1 -1
- package/dist/classes/Api.js +87 -14
- package/dist/classes/Api.js.map +1 -1
- package/dist/classes/BorrowableCToken.d.ts +9 -1
- package/dist/classes/BorrowableCToken.d.ts.map +1 -1
- package/dist/classes/BorrowableCToken.js +67 -12
- package/dist/classes/BorrowableCToken.js.map +1 -1
- package/dist/classes/CToken.d.ts +75 -18
- package/dist/classes/CToken.d.ts.map +1 -1
- package/dist/classes/CToken.js +638 -269
- package/dist/classes/CToken.js.map +1 -1
- package/dist/classes/Calldata.d.ts +2 -2
- package/dist/classes/Calldata.d.ts.map +1 -1
- package/dist/classes/Calldata.js +6 -2
- package/dist/classes/Calldata.js.map +1 -1
- package/dist/classes/DexAggregators/IDexAgg.d.ts +1 -1
- package/dist/classes/DexAggregators/Kuru.js +1 -1
- package/dist/classes/DexAggregators/Kuru.js.map +1 -1
- package/dist/classes/DexAggregators/KyberSwap.d.ts.map +1 -1
- package/dist/classes/DexAggregators/KyberSwap.js +112 -61
- package/dist/classes/DexAggregators/KyberSwap.js.map +1 -1
- package/dist/classes/DexAggregators/MultiDexAgg.d.ts +2 -2
- package/dist/classes/DexAggregators/MultiDexAgg.d.ts.map +1 -1
- package/dist/classes/DexAggregators/MultiDexAgg.js +30 -18
- package/dist/classes/DexAggregators/MultiDexAgg.js.map +1 -1
- package/dist/classes/DexAggregators/UnsupportedDexAgg.d.ts +19 -0
- package/dist/classes/DexAggregators/UnsupportedDexAgg.d.ts.map +1 -0
- package/dist/classes/DexAggregators/UnsupportedDexAgg.js +29 -0
- package/dist/classes/DexAggregators/UnsupportedDexAgg.js.map +1 -0
- package/dist/classes/DexAggregators/helpers.d.ts +30 -0
- package/dist/classes/DexAggregators/helpers.d.ts.map +1 -0
- package/dist/classes/DexAggregators/helpers.js +57 -0
- package/dist/classes/DexAggregators/helpers.js.map +1 -0
- package/dist/classes/DexAggregators/index.d.ts +2 -0
- package/dist/classes/DexAggregators/index.d.ts.map +1 -1
- package/dist/classes/DexAggregators/index.js +1 -0
- package/dist/classes/DexAggregators/index.js.map +1 -1
- package/dist/classes/ERC20.d.ts +3 -3
- package/dist/classes/ERC20.d.ts.map +1 -1
- package/dist/classes/ERC20.js +26 -10
- package/dist/classes/ERC20.js.map +1 -1
- package/dist/classes/FormatConverter.d.ts.map +1 -1
- package/dist/classes/FormatConverter.js +4 -1
- package/dist/classes/FormatConverter.js.map +1 -1
- package/dist/classes/Market.d.ts +40 -6
- package/dist/classes/Market.d.ts.map +1 -1
- package/dist/classes/Market.js +333 -106
- package/dist/classes/Market.js.map +1 -1
- package/dist/classes/NativeToken.d.ts +2 -2
- package/dist/classes/NativeToken.d.ts.map +1 -1
- package/dist/classes/NativeToken.js +21 -7
- package/dist/classes/NativeToken.js.map +1 -1
- package/dist/classes/OptimizerReader.d.ts +15 -7
- package/dist/classes/OptimizerReader.d.ts.map +1 -1
- package/dist/classes/OptimizerReader.js +108 -30
- package/dist/classes/OptimizerReader.js.map +1 -1
- package/dist/classes/OracleManager.d.ts.map +1 -1
- package/dist/classes/OracleManager.js +11 -4
- package/dist/classes/OracleManager.js.map +1 -1
- package/dist/classes/PositionManager.d.ts.map +1 -1
- package/dist/classes/PositionManager.js +1 -12
- package/dist/classes/PositionManager.js.map +1 -1
- package/dist/classes/ProtocolReader.d.ts +30 -9
- package/dist/classes/ProtocolReader.d.ts.map +1 -1
- package/dist/classes/ProtocolReader.js +158 -138
- package/dist/classes/ProtocolReader.js.map +1 -1
- package/dist/classes/Zapper.d.ts +5 -5
- package/dist/classes/Zapper.d.ts.map +1 -1
- package/dist/classes/Zapper.js +21 -18
- package/dist/classes/Zapper.js.map +1 -1
- package/dist/contracts/monad-mainnet.json +1 -1
- package/dist/feePolicy.d.ts +2 -0
- package/dist/feePolicy.d.ts.map +1 -1
- package/dist/feePolicy.js +19 -0
- package/dist/feePolicy.js.map +1 -1
- package/dist/format/borrow.d.ts.map +1 -1
- package/dist/format/borrow.js +7 -1
- package/dist/format/borrow.js.map +1 -1
- package/dist/format/health.d.ts +3 -2
- package/dist/format/health.d.ts.map +1 -1
- package/dist/format/health.js +10 -9
- package/dist/format/health.js.map +1 -1
- package/dist/format/leverage.js +5 -4
- package/dist/format/leverage.js.map +1 -1
- package/dist/helpers.d.ts +14 -11
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +171 -52
- package/dist/helpers.js.map +1 -1
- package/dist/integrations/merkl.d.ts +2 -1
- package/dist/integrations/merkl.d.ts.map +1 -1
- package/dist/integrations/merkl.js +199 -4
- package/dist/integrations/merkl.js.map +1 -1
- package/dist/integrations/snapshot.d.ts +28 -24
- package/dist/integrations/snapshot.d.ts.map +1 -1
- package/dist/integrations/snapshot.js +106 -42
- package/dist/integrations/snapshot.js.map +1 -1
- package/dist/retry-provider.d.ts +6 -0
- package/dist/retry-provider.d.ts.map +1 -1
- package/dist/retry-provider.js +103 -11
- package/dist/retry-provider.js.map +1 -1
- package/dist/setup.d.ts +8 -6
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +125 -45
- package/dist/setup.js.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +13 -7
- package/dist/validation.js.map +1 -1
- package/package.json +6 -3
package/dist/classes/CToken.js
CHANGED
|
@@ -19,6 +19,7 @@ const FormatConverter_1 = __importDefault(require("./FormatConverter"));
|
|
|
19
19
|
const EXCLUDED_ZAP_SYMBOLS = new Set([
|
|
20
20
|
'eBTC', 'earnAUSD', 'vUSD', 'syzUSD', 'ezETH', 'YZM', 'wsrUSD', 'sAUSD',
|
|
21
21
|
]);
|
|
22
|
+
const EXECUTION_DEBT_BUFFER_TIME = 100n;
|
|
22
23
|
/**
|
|
23
24
|
* Leverage operation buffers — centralized for tuning.
|
|
24
25
|
* Calibrated for fresh-state operation via getLeverageSnapshot under
|
|
@@ -113,7 +114,7 @@ exports.LEVERAGE = {
|
|
|
113
114
|
* for that amplification (see leverageDown). Bump when aggregator fees
|
|
114
115
|
* are enabled to keep dust prevention reliable. */
|
|
115
116
|
DELEVERAGE_OVERHEAD_BPS: 20n,
|
|
116
|
-
/** BPS buffer on
|
|
117
|
+
/** BPS buffer on expected-share calculations for zap/leverage paths.
|
|
117
118
|
* Covers exchange rate drift from interest accrual since cache load. */
|
|
118
119
|
SHARES_BUFFER_BPS: 2n,
|
|
119
120
|
/** Per-leverage-unit BPS buffer for `checkSlippage` on vault + native-vault
|
|
@@ -133,6 +134,24 @@ exports.LEVERAGE = {
|
|
|
133
134
|
* and tx-time state, not between two independent feeds. */
|
|
134
135
|
LEVERAGE_UP_VAULT_DRIFT_BPS: 30n,
|
|
135
136
|
};
|
|
137
|
+
const USER_CACHE_FIELDS = [
|
|
138
|
+
"userAssetBalance",
|
|
139
|
+
"userShareBalance",
|
|
140
|
+
"userUnderlyingBalance",
|
|
141
|
+
"userCollateral",
|
|
142
|
+
"userDebt",
|
|
143
|
+
"liquidationPrice",
|
|
144
|
+
];
|
|
145
|
+
function createUserCacheFreshness(value) {
|
|
146
|
+
return {
|
|
147
|
+
userAssetBalance: value,
|
|
148
|
+
userShareBalance: value,
|
|
149
|
+
userUnderlyingBalance: value,
|
|
150
|
+
userCollateral: value,
|
|
151
|
+
userDebt: value,
|
|
152
|
+
liquidationPrice: value,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
136
155
|
class CToken extends Calldata_1.Calldata {
|
|
137
156
|
provider;
|
|
138
157
|
address;
|
|
@@ -148,13 +167,14 @@ class CToken extends Calldata_1.Calldata {
|
|
|
148
167
|
nativeApy = (0, decimal_js_1.default)(0);
|
|
149
168
|
incentiveSupplyApy = (0, decimal_js_1.default)(0);
|
|
150
169
|
incentiveBorrowApy = (0, decimal_js_1.default)(0);
|
|
170
|
+
userCacheFreshness;
|
|
151
171
|
get signer() { return this.market.signer; }
|
|
152
172
|
get account() { return this.market.account; }
|
|
153
173
|
constructor(provider, address, cache, market) {
|
|
154
174
|
super();
|
|
155
175
|
this.provider = provider;
|
|
156
176
|
this.address = address;
|
|
157
|
-
this.contract = (0, helpers_1.contractSetup)(provider, address, BaseCToken_json_1.default);
|
|
177
|
+
this.contract = (0, helpers_1.contractSetup)(this.provider, address, BaseCToken_json_1.default);
|
|
158
178
|
this.cache = cache;
|
|
159
179
|
this.market = market;
|
|
160
180
|
const chainSettings = this.currentChainConfig;
|
|
@@ -179,6 +199,31 @@ class CToken extends Calldata_1.Calldata {
|
|
|
179
199
|
this.leverageTypes.push('simple');
|
|
180
200
|
this.zapTypes.push('simple');
|
|
181
201
|
}
|
|
202
|
+
getUserCacheFreshness() {
|
|
203
|
+
if (this.userCacheFreshness == undefined) {
|
|
204
|
+
this.userCacheFreshness = createUserCacheFreshness(true);
|
|
205
|
+
}
|
|
206
|
+
return this.userCacheFreshness;
|
|
207
|
+
}
|
|
208
|
+
markUserCacheFresh(fields = USER_CACHE_FIELDS) {
|
|
209
|
+
const freshness = this.getUserCacheFreshness();
|
|
210
|
+
for (const field of fields) {
|
|
211
|
+
freshness[field] = true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
invalidateUserCache(fields = USER_CACHE_FIELDS) {
|
|
215
|
+
const freshness = this.getUserCacheFreshness();
|
|
216
|
+
for (const field of fields) {
|
|
217
|
+
freshness[field] = false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
readFreshUserCache(field, accessLabel) {
|
|
221
|
+
if (!this.getUserCacheFreshness()[field]) {
|
|
222
|
+
throw new Error(`Token-level user data is stale for ${this.address} after a summary-only refresh on market ${this.market.address}. ` +
|
|
223
|
+
`Call market.reloadUserData(account) or Market.reloadUserMarkets(...) before ${accessLabel}.`);
|
|
224
|
+
}
|
|
225
|
+
return this.cache[field];
|
|
226
|
+
}
|
|
182
227
|
get setup() { return this.market.setup; }
|
|
183
228
|
get currentChain() { return this.setup.chain; }
|
|
184
229
|
get currentChainConfig() { return (0, helpers_1.getChainConfig)(this.currentChain); }
|
|
@@ -189,6 +234,49 @@ class CToken extends Calldata_1.Calldata {
|
|
|
189
234
|
getWriteContract() {
|
|
190
235
|
return (0, helpers_1.contractSetup)(this.requireSigner(), this.address, BaseCToken_json_1.default);
|
|
191
236
|
}
|
|
237
|
+
getZapType(zap) {
|
|
238
|
+
return typeof zap === "object" ? zap.type : zap;
|
|
239
|
+
}
|
|
240
|
+
isZapInstruction(zap) {
|
|
241
|
+
return this.getZapType(zap) !== "none";
|
|
242
|
+
}
|
|
243
|
+
async getZapInputDecimals(zap) {
|
|
244
|
+
const zapType = this.getZapType(zap);
|
|
245
|
+
switch (zapType) {
|
|
246
|
+
case "none":
|
|
247
|
+
return this.asset.decimals;
|
|
248
|
+
case "native-vault":
|
|
249
|
+
case "native-simple":
|
|
250
|
+
return 18n;
|
|
251
|
+
case "vault": {
|
|
252
|
+
const vaultAsset = await this.getVaultAsset(true);
|
|
253
|
+
return vaultAsset.decimals ?? await vaultAsset.contract.decimals();
|
|
254
|
+
}
|
|
255
|
+
case "simple":
|
|
256
|
+
if (typeof zap !== "object") {
|
|
257
|
+
return this.asset.decimals;
|
|
258
|
+
}
|
|
259
|
+
if (zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
260
|
+
return 18n;
|
|
261
|
+
}
|
|
262
|
+
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, undefined, this.signer);
|
|
263
|
+
return inputErc20.decimals ?? await inputErc20.contract.decimals();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async getZapAssetAmount(amount, zap) {
|
|
267
|
+
return FormatConverter_1.default.decimalToBigInt(amount, await this.getZapInputDecimals(zap));
|
|
268
|
+
}
|
|
269
|
+
async assertVaultLeverageBorrowAssetSupported(borrow, type) {
|
|
270
|
+
const expectedAsset = type === "native-vault"
|
|
271
|
+
? this.currentChainConfig.wrapped_native
|
|
272
|
+
: await this.getVaultAsset(false);
|
|
273
|
+
const actualAsset = borrow.asset.address;
|
|
274
|
+
if (actualAsset.toLowerCase() === expectedAsset.toLowerCase()) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
throw new Error(`${type} leverage requires borrow asset ${expectedAsset}, received ${actualAsset}. ` +
|
|
278
|
+
`Use simple leverage for cross-asset borrow routes.`);
|
|
279
|
+
}
|
|
192
280
|
get adapters() { return this.cache.adapters; }
|
|
193
281
|
get borrowPaused() { return this.cache.borrowPaused; }
|
|
194
282
|
get collateralizationPaused() { return this.cache.collateralizationPaused; }
|
|
@@ -213,17 +301,30 @@ class CToken extends Calldata_1.Calldata {
|
|
|
213
301
|
get totalAssets() { return this.cache.totalAssets; }
|
|
214
302
|
get totalSupply() { return this.cache.totalSupply; }
|
|
215
303
|
get liquidationPrice() {
|
|
216
|
-
|
|
304
|
+
const liquidationPrice = this.readFreshUserCache("liquidationPrice", "reading token liquidationPrice");
|
|
305
|
+
if (liquidationPrice == helpers_1.UINT256_MAX)
|
|
217
306
|
return null;
|
|
218
|
-
return (0, helpers_1.toDecimal)(
|
|
307
|
+
return (0, helpers_1.toDecimal)(liquidationPrice, 18n);
|
|
219
308
|
}
|
|
220
309
|
get irmTargetRate() { return (0, decimal_js_1.default)(this.cache.irmTargetRate).div(helpers_1.WAD); }
|
|
221
310
|
get irmMaxRate() { return (0, decimal_js_1.default)(this.cache.irmMaxRate).div(helpers_1.WAD); }
|
|
222
311
|
get irmTargetUtilization() { return (0, decimal_js_1.default)(this.cache.irmTargetUtilization).div(helpers_1.WAD); }
|
|
223
312
|
get interestFee() { return (0, decimal_js_1.default)(this.cache.interestFee).div(helpers_1.BPS); }
|
|
224
313
|
virtualConvertToAssets(shares) {
|
|
314
|
+
if (this.totalSupply === 0n || this.totalAssets === 0n) {
|
|
315
|
+
return shares;
|
|
316
|
+
}
|
|
225
317
|
return (shares * this.totalAssets) / this.totalSupply;
|
|
226
318
|
}
|
|
319
|
+
formatAssets(assets) {
|
|
320
|
+
return FormatConverter_1.default.bigIntToDecimal(assets, this.asset.decimals);
|
|
321
|
+
}
|
|
322
|
+
formatShares(shares) {
|
|
323
|
+
return FormatConverter_1.default.bigIntToDecimal(shares, this.decimals);
|
|
324
|
+
}
|
|
325
|
+
formatSharesAsAssets(shares) {
|
|
326
|
+
return this.formatAssets(this.virtualConvertToAssets(shares));
|
|
327
|
+
}
|
|
227
328
|
/**
|
|
228
329
|
* Convert assets to shares using cached totalSupply/totalAssets.
|
|
229
330
|
* @param bufferBps Optional downward buffer in BPS to account for
|
|
@@ -231,19 +332,35 @@ class CToken extends Calldata_1.Calldata {
|
|
|
231
332
|
* Matches the buffer pattern in async convertToShares().
|
|
232
333
|
*/
|
|
233
334
|
virtualConvertToShares(assets, bufferBps = 0n) {
|
|
234
|
-
const shares =
|
|
335
|
+
const shares = this.totalSupply === 0n || this.totalAssets === 0n
|
|
336
|
+
? assets
|
|
337
|
+
: (assets * this.totalSupply) / this.totalAssets;
|
|
235
338
|
return bufferBps > 0n ? shares * (10000n - bufferBps) / 10000n : shares;
|
|
236
339
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
340
|
+
getMarketLeverageState() {
|
|
341
|
+
const currentCollateralInUsd = this.market.userCollateral;
|
|
342
|
+
const currentDebt = this.market.userDebt;
|
|
343
|
+
const equity = currentCollateralInUsd.sub(currentDebt);
|
|
344
|
+
if (currentCollateralInUsd.lte(0) || equity.lte(0)) {
|
|
345
|
+
return {
|
|
346
|
+
currentCollateralInUsd,
|
|
347
|
+
currentDebt,
|
|
348
|
+
currentLeverage: null,
|
|
349
|
+
};
|
|
240
350
|
}
|
|
241
|
-
const
|
|
242
|
-
return
|
|
351
|
+
const currentLeverage = currentCollateralInUsd.div(equity);
|
|
352
|
+
return {
|
|
353
|
+
currentCollateralInUsd,
|
|
354
|
+
currentDebt,
|
|
355
|
+
currentLeverage: currentLeverage.eq(1) ? null : currentLeverage,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
getLeverage() {
|
|
359
|
+
return this.getMarketLeverageState().currentLeverage;
|
|
243
360
|
}
|
|
244
361
|
getRemainingCollateral(formatted = true) {
|
|
245
362
|
const diff = this.cache.collateralCap - this.cache.collateral;
|
|
246
|
-
return formatted ? this.
|
|
363
|
+
return formatted ? this.convertSharesToUsdSync(diff) : diff;
|
|
247
364
|
}
|
|
248
365
|
getRemainingDebt(formatted = true) {
|
|
249
366
|
const diff = this.cache.debtCap - this.cache.debt;
|
|
@@ -283,36 +400,52 @@ class CToken extends Calldata_1.Calldata {
|
|
|
283
400
|
return inBPS ? (0, decimal_js_1.default)(this.cache.closeFactorMax).div(helpers_1.BPS) : this.cache.closeFactorMax;
|
|
284
401
|
}
|
|
285
402
|
getUserShareBalance(inUSD) {
|
|
286
|
-
|
|
403
|
+
const userShareBalance = this.readFreshUserCache("userShareBalance", "reading token user share balance");
|
|
404
|
+
return inUSD ? this.convertTokensToUsd(userShareBalance, false) : this.formatShares(userShareBalance);
|
|
405
|
+
}
|
|
406
|
+
getUserShareBalanceAssets() {
|
|
407
|
+
const userShareBalance = this.readFreshUserCache("userShareBalance", "reading token user share balance as assets");
|
|
408
|
+
return this.formatSharesAsAssets(userShareBalance);
|
|
287
409
|
}
|
|
288
410
|
getUserAssetBalance(inUSD) {
|
|
289
|
-
|
|
411
|
+
const userAssetBalance = this.readFreshUserCache("userAssetBalance", "reading token user asset balance");
|
|
412
|
+
return inUSD ? this.convertTokensToUsd(userAssetBalance) : this.formatAssets(userAssetBalance);
|
|
290
413
|
}
|
|
291
414
|
getUserUnderlyingBalance(inUSD) {
|
|
292
|
-
|
|
415
|
+
const userUnderlyingBalance = this.readFreshUserCache("userUnderlyingBalance", "reading token user underlying balance");
|
|
416
|
+
return inUSD ? this.convertTokensToUsd(userUnderlyingBalance) : this.formatAssets(userUnderlyingBalance);
|
|
293
417
|
}
|
|
294
418
|
getCollateralCap(inUSD) {
|
|
295
|
-
return inUSD ? this.
|
|
419
|
+
return inUSD ? this.convertSharesToUsdSync(this.cache.collateralCap) : this.cache.collateralCap;
|
|
296
420
|
}
|
|
297
421
|
getDebtCap(inUSD) {
|
|
298
422
|
return inUSD ? this.convertTokensToUsd(this.cache.debtCap) : this.cache.debtCap;
|
|
299
423
|
}
|
|
300
424
|
getCollateral(inUSD) {
|
|
301
|
-
return inUSD ? this.
|
|
425
|
+
return inUSD ? this.convertSharesToUsdSync(this.cache.collateral) : this.cache.collateral;
|
|
302
426
|
}
|
|
303
427
|
getDebt(inUSD) {
|
|
304
428
|
return inUSD ? this.convertTokensToUsd(this.cache.debt) : this.cache.debt;
|
|
305
429
|
}
|
|
306
430
|
getUserCollateral(inUSD) {
|
|
307
|
-
|
|
431
|
+
const userCollateral = this.getUserCollateralShares();
|
|
432
|
+
return inUSD ? this.convertTokensToUsd(userCollateral, false) : this.formatShares(userCollateral);
|
|
433
|
+
}
|
|
434
|
+
getUserCollateralShares() {
|
|
435
|
+
return this.readFreshUserCache("userCollateral", "reading token user collateral shares");
|
|
436
|
+
}
|
|
437
|
+
getUserCollateralAssets() {
|
|
438
|
+
return this.formatSharesAsAssets(this.getUserCollateralShares());
|
|
308
439
|
}
|
|
309
440
|
async fetchUserCollateral(formatted = false) {
|
|
310
441
|
const collateral = await this.contract.collateralPosted(this.getAccountOrThrow());
|
|
311
442
|
this.cache.userCollateral = collateral;
|
|
443
|
+
this.markUserCacheFresh(["userCollateral"]);
|
|
312
444
|
return formatted ? (0, helpers_1.toDecimal)(collateral, this.decimals) : collateral;
|
|
313
445
|
}
|
|
314
446
|
getUserDebt(inUSD) {
|
|
315
|
-
|
|
447
|
+
const userDebt = this.readFreshUserCache("userDebt", "reading token user debt");
|
|
448
|
+
return inUSD ? this.convertTokensToUsd(userDebt) : FormatConverter_1.default.bigIntToDecimal(userDebt, this.asset.decimals);
|
|
316
449
|
}
|
|
317
450
|
earnChange(amount, rateType) {
|
|
318
451
|
const rate = this.getApy(false);
|
|
@@ -336,6 +469,16 @@ class CToken extends Calldata_1.Calldata {
|
|
|
336
469
|
async getVaultAsset(asErc20) {
|
|
337
470
|
return asErc20 ? await this.getUnderlyingVault().fetchAsset(true) : await this.getUnderlyingVault().fetchAsset(false);
|
|
338
471
|
}
|
|
472
|
+
async getExpectedVaultShares(assets) {
|
|
473
|
+
const vault = this.getUnderlyingVault();
|
|
474
|
+
const vaultSharesRaw = await vault.previewDeposit(assets);
|
|
475
|
+
// Vault/native-vault flows mint vault shares first, then convert those
|
|
476
|
+
// into Curvance shares. Buffer the inner preview so exchange-rate drift
|
|
477
|
+
// between quote time and inclusion cannot trip the outer expectedShares
|
|
478
|
+
// check on otherwise-valid deposits/leverage/zaps.
|
|
479
|
+
const vaultShares = vaultSharesRaw * (10000n - exports.LEVERAGE.SHARES_BUFFER_BPS) / 10000n;
|
|
480
|
+
return this.convertToShares(vaultShares);
|
|
481
|
+
}
|
|
339
482
|
getAsset(asErc20) {
|
|
340
483
|
return asErc20
|
|
341
484
|
? new ERC20_1.ERC20(this.provider, this.cache.asset.address, this.cache.asset, this.setup.contracts.OracleManager, this.signer)
|
|
@@ -376,11 +519,11 @@ class CToken extends Calldata_1.Calldata {
|
|
|
376
519
|
}
|
|
377
520
|
getTotalCollateral(inUSD = true) {
|
|
378
521
|
const totalCollateral = this.cache.collateral;
|
|
379
|
-
return inUSD ? this.
|
|
522
|
+
return inUSD ? this.convertSharesToUsdSync(totalCollateral) : totalCollateral;
|
|
380
523
|
}
|
|
381
524
|
async fetchTotalCollateral(inUSD = true) {
|
|
382
525
|
const totalCollateral = await this.contract.marketCollateralPosted();
|
|
383
|
-
return inUSD ? this.
|
|
526
|
+
return inUSD ? this.fetchConvertSharesToUsd(totalCollateral) : totalCollateral;
|
|
384
527
|
}
|
|
385
528
|
getPositionManager(type) {
|
|
386
529
|
const signer = this.requireSigner();
|
|
@@ -399,29 +542,24 @@ class CToken extends Calldata_1.Calldata {
|
|
|
399
542
|
return new Zapper_1.Zapper(zap_contract, signer, type, this.setup);
|
|
400
543
|
}
|
|
401
544
|
async isZapAssetApproved(instructions, amount) {
|
|
402
|
-
if (instructions == 'none'
|
|
545
|
+
if (instructions == 'none') {
|
|
403
546
|
return true;
|
|
404
547
|
}
|
|
405
|
-
|
|
548
|
+
const approvalTarget = await this.resolveZapApprovalTarget(instructions);
|
|
549
|
+
if (approvalTarget == null) {
|
|
406
550
|
return true;
|
|
407
551
|
}
|
|
408
|
-
|
|
409
|
-
const asset = new ERC20_1.ERC20(this.provider, instructions.inputToken, undefined, undefined, signer);
|
|
410
|
-
const plugin = this.getPluginAddress(instructions.type, 'zapper');
|
|
411
|
-
const allowance = await asset.allowance(signer.address, plugin);
|
|
412
|
-
return allowance >= amount;
|
|
552
|
+
return this.hasTokenApproval(approvalTarget, amount);
|
|
413
553
|
}
|
|
414
554
|
async approveZapAsset(instructions, amount) {
|
|
415
|
-
if (instructions == 'none'
|
|
555
|
+
if (instructions == 'none') {
|
|
416
556
|
throw new Error("Plugin does not have an associated contract");
|
|
417
557
|
}
|
|
418
|
-
|
|
558
|
+
const approvalTarget = await this.resolveZapApprovalTarget(instructions);
|
|
559
|
+
if (approvalTarget == null) {
|
|
419
560
|
return;
|
|
420
561
|
}
|
|
421
|
-
|
|
422
|
-
const asset = new ERC20_1.ERC20(this.provider, instructions.inputToken, undefined, undefined, signer);
|
|
423
|
-
const plugin = this.getPluginAddress(instructions.type, 'zapper');
|
|
424
|
-
return asset.approve(plugin, amount);
|
|
562
|
+
return approvalTarget.token.approve(approvalTarget.spender, amount);
|
|
425
563
|
}
|
|
426
564
|
async isPluginApproved(plugin, type) {
|
|
427
565
|
if (plugin == 'none') {
|
|
@@ -561,8 +699,22 @@ class CToken extends Calldata_1.Calldata {
|
|
|
561
699
|
receiver ??= signer.address;
|
|
562
700
|
owner ??= signer.address;
|
|
563
701
|
const shares = this.convertTokenInputToShares(amount);
|
|
564
|
-
const
|
|
565
|
-
|
|
702
|
+
const signerAddress = signer.address;
|
|
703
|
+
let method = "redeemCollateral";
|
|
704
|
+
if (owner.toLowerCase() !== signerAddress.toLowerCase()) {
|
|
705
|
+
const isDelegated = await this.contract.isDelegate(owner, signerAddress);
|
|
706
|
+
if (isDelegated) {
|
|
707
|
+
method = "redeemCollateralFor";
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
const allowance = await this.contract.allowance(owner, signerAddress);
|
|
711
|
+
if (allowance < shares) {
|
|
712
|
+
throw new Error(`Please approve ${this.symbol} shares for ${signerAddress} or delegate ${signerAddress} before redeeming collateral for ${owner}.`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const calldata = this.getCallData(method, [shares, receiver, owner]);
|
|
717
|
+
return this.oracleRoute(calldata, {}, owner);
|
|
566
718
|
}
|
|
567
719
|
async postCollateral(amount) {
|
|
568
720
|
const signer = this.requireSigner();
|
|
@@ -571,6 +723,9 @@ class CToken extends Calldata_1.Calldata {
|
|
|
571
723
|
const collateral = await this.fetchUserCollateral();
|
|
572
724
|
const available_shares = balance - collateral;
|
|
573
725
|
const max_shares = available_shares < shares ? available_shares : shares;
|
|
726
|
+
if (max_shares <= 0n) {
|
|
727
|
+
throw new Error("No cToken shares available to post as collateral.");
|
|
728
|
+
}
|
|
574
729
|
const calldata = this.getCallData("postCollateral", [max_shares]);
|
|
575
730
|
const tx = await this.oracleRoute(calldata);
|
|
576
731
|
// Reload collateral state after execution
|
|
@@ -607,56 +762,56 @@ class CToken extends Calldata_1.Calldata {
|
|
|
607
762
|
}
|
|
608
763
|
return asset.balanceOf(signer.address, false);
|
|
609
764
|
}
|
|
610
|
-
// TODO: Hack to remove
|
|
611
765
|
async ensureUnderlyingAmount(amount, zap) {
|
|
612
766
|
const balance = await this.getZapBalance(zap);
|
|
613
|
-
const
|
|
614
|
-
// Use the zap input token's decimals when zapping, otherwise the deposit token's decimals
|
|
615
|
-
let decimals = this.asset.decimals;
|
|
616
|
-
if (isZapping && zap.inputToken) {
|
|
617
|
-
if (zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
618
|
-
decimals = 18n;
|
|
619
|
-
}
|
|
620
|
-
else {
|
|
621
|
-
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, undefined, this.signer);
|
|
622
|
-
decimals = inputErc20.decimals ?? await inputErc20.contract.decimals();
|
|
623
|
-
}
|
|
624
|
-
}
|
|
767
|
+
const decimals = await this.getZapInputDecimals(zap);
|
|
625
768
|
const assets = FormatConverter_1.default.decimalToBigInt(amount, decimals);
|
|
626
769
|
if (assets > balance) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
formatted: FormatConverter_1.default.bigIntToDecimal(balance, decimals),
|
|
630
|
-
attempt: {
|
|
631
|
-
raw: assets,
|
|
632
|
-
formatted: amount
|
|
633
|
-
},
|
|
634
|
-
});
|
|
635
|
-
return FormatConverter_1.default.bigIntToDecimal(balance, decimals);
|
|
770
|
+
const formattedBalance = FormatConverter_1.default.bigIntToDecimal(balance, decimals);
|
|
771
|
+
throw new Error(`Insufficient balance: requested ${amount.toString()}, available ${formattedBalance.toString()}.`);
|
|
636
772
|
}
|
|
637
773
|
return amount;
|
|
638
774
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
775
|
+
getExecutionDebtBufferTime() {
|
|
776
|
+
return this.market.userDebt.greaterThan(0) ? EXECUTION_DEBT_BUFFER_TIME : 0n;
|
|
777
|
+
}
|
|
778
|
+
async resolveCollateralRemovalShares(amount) {
|
|
779
|
+
const max_removable_shares = await this.maxRemovableCollateral(true, this.getExecutionDebtBufferTime());
|
|
780
|
+
const requested_shares = this.convertTokenInputToShares(amount);
|
|
781
|
+
let shares = max_removable_shares < requested_shares ? max_removable_shares : requested_shares;
|
|
782
|
+
// If within 0.1% of the safe removable collateral, remove it all to avoid dust.
|
|
783
|
+
const threshold = max_removable_shares / 1000n || 10n;
|
|
784
|
+
if (max_removable_shares - shares <= threshold) {
|
|
785
|
+
shares = max_removable_shares;
|
|
644
786
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if (current_shares - max_shares <= threshold) {
|
|
651
|
-
max_shares = current_shares;
|
|
652
|
-
}
|
|
787
|
+
return shares;
|
|
788
|
+
}
|
|
789
|
+
async executeCollateralRemoval(shares) {
|
|
790
|
+
if (shares === 0n) {
|
|
791
|
+
throw new Error("No removable collateral available.");
|
|
653
792
|
}
|
|
654
|
-
const calldata = this.getCallData("removeCollateral", [
|
|
793
|
+
const calldata = this.getCallData("removeCollateral", [shares]);
|
|
655
794
|
const tx = await this.oracleRoute(calldata);
|
|
656
795
|
// Reload collateral state after execution
|
|
657
796
|
await this.fetchUserCollateral();
|
|
658
797
|
return tx;
|
|
659
798
|
}
|
|
799
|
+
async maxRemovableCollateral(in_shares = false, bufferTime = 0n) {
|
|
800
|
+
if (in_shares) {
|
|
801
|
+
const breakdown = await this.maxRedemption(true, bufferTime, true);
|
|
802
|
+
return breakdown.max_collateral;
|
|
803
|
+
}
|
|
804
|
+
const breakdown = await this.maxRedemption(false, bufferTime, true);
|
|
805
|
+
return breakdown.max_collateral;
|
|
806
|
+
}
|
|
807
|
+
async removeCollateralExact(amount) {
|
|
808
|
+
const shares = await this.resolveCollateralRemovalShares(amount);
|
|
809
|
+
return this.executeCollateralRemoval(shares);
|
|
810
|
+
}
|
|
811
|
+
async removeMaxCollateral() {
|
|
812
|
+
const shares = await this.maxRemovableCollateral(true, this.getExecutionDebtBufferTime());
|
|
813
|
+
return this.executeCollateralRemoval(shares);
|
|
814
|
+
}
|
|
660
815
|
convertTokenInputToShares(amount) {
|
|
661
816
|
return this.virtualConvertToShares(FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals));
|
|
662
817
|
}
|
|
@@ -677,7 +832,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
677
832
|
async convertToAssets(shares) {
|
|
678
833
|
return this.contract.convertToAssets(shares);
|
|
679
834
|
}
|
|
680
|
-
async convertToShares(assets, bufferBps =
|
|
835
|
+
async convertToShares(assets, bufferBps = exports.LEVERAGE.SHARES_BUFFER_BPS) {
|
|
681
836
|
const shares = await this.contract.convertToShares(assets);
|
|
682
837
|
return bufferBps > 0n ? shares * (10000n - bufferBps) / 10000n : shares;
|
|
683
838
|
}
|
|
@@ -711,7 +866,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
711
866
|
interface: new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account),
|
|
712
867
|
type: 'native-vault'
|
|
713
868
|
});
|
|
714
|
-
tokens_exclude.push(helpers_1.EMPTY_ADDRESS, helpers_1.NATIVE_ADDRESS);
|
|
869
|
+
tokens_exclude.push(helpers_1.EMPTY_ADDRESS.toLowerCase(), helpers_1.NATIVE_ADDRESS.toLowerCase());
|
|
715
870
|
}
|
|
716
871
|
if (this.zapTypes.includes('native-simple')) {
|
|
717
872
|
tokens.push({
|
|
@@ -719,7 +874,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
719
874
|
type: 'native-simple'
|
|
720
875
|
});
|
|
721
876
|
if (!this.zapTypes.includes('native-vault')) {
|
|
722
|
-
tokens_exclude.push(helpers_1.EMPTY_ADDRESS, helpers_1.NATIVE_ADDRESS);
|
|
877
|
+
tokens_exclude.push(helpers_1.EMPTY_ADDRESS.toLowerCase(), helpers_1.NATIVE_ADDRESS.toLowerCase());
|
|
723
878
|
}
|
|
724
879
|
}
|
|
725
880
|
if (this.zapTypes.includes('vault')) {
|
|
@@ -730,7 +885,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
730
885
|
});
|
|
731
886
|
tokens_exclude.push(vault_asset.address.toLocaleLowerCase());
|
|
732
887
|
}
|
|
733
|
-
if (this.zapTypes.includes('simple')) {
|
|
888
|
+
if (this.zapTypes.includes('simple') && this.currentChainConfig.dexAgg.router !== helpers_1.EMPTY_ADDRESS) {
|
|
734
889
|
let dexAggSearch = await this.currentChainConfig.dexAgg.getAvailableTokens(this.provider, search, this.account);
|
|
735
890
|
tokens = tokens.concat(dexAggSearch.filter(token => !tokens_exclude.includes(token.interface.address.toLocaleLowerCase())));
|
|
736
891
|
// Add native MON as a zap option for any token with a simple zapper
|
|
@@ -774,6 +929,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
774
929
|
this.cache.assetPrice = snapshot.collateralAssetPrice;
|
|
775
930
|
this.cache.sharePrice = snapshot.sharePrice;
|
|
776
931
|
borrow.cache.assetPrice = snapshot.debtAssetPrice;
|
|
932
|
+
this.market.cache.user.collateral = snapshot.collateralUsd;
|
|
777
933
|
this.market.cache.user.debt = snapshot.debtUsd;
|
|
778
934
|
return snapshot;
|
|
779
935
|
}
|
|
@@ -804,41 +960,116 @@ class CToken extends Calldata_1.Calldata {
|
|
|
804
960
|
return slippage;
|
|
805
961
|
return slippage + exports.LEVERAGE.LEVERAGE_UP_BUFFER_BPS;
|
|
806
962
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
963
|
+
assertSimpleLeverageSwapAssetsDiffer(borrow) {
|
|
964
|
+
if (borrow.asset.address.toLowerCase() === this.asset.address.toLowerCase()) {
|
|
965
|
+
throw new Error("Simple leverage requires distinct collateral and borrow assets.");
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
async assertLeverageBorrowCapacity(borrow, borrowAssets) {
|
|
969
|
+
if (borrowAssets === 0n) {
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
const [assetsHeld, outstandingDebt] = await Promise.all([
|
|
973
|
+
borrow.fetchLiquidity(),
|
|
974
|
+
borrow.marketOutstandingDebt(),
|
|
975
|
+
]);
|
|
976
|
+
const remainingDebtCap = borrow.cache.debtCap > outstandingDebt
|
|
977
|
+
? borrow.cache.debtCap - outstandingDebt
|
|
978
|
+
: 0n;
|
|
979
|
+
const capacity = assetsHeld < remainingDebtCap ? assetsHeld : remainingDebtCap;
|
|
980
|
+
if (borrowAssets > capacity) {
|
|
981
|
+
throw new Error("Selected borrow token does not have enough remaining debt capacity or liquidity for this leverage operation.");
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
assertSelectedBorrowDebtCanDeleverage(borrow, selectedDebtAssets, requiredDebtReductionUsd) {
|
|
985
|
+
if (requiredDebtReductionUsd.lte(0)) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
const price = borrow.getPrice(true);
|
|
989
|
+
if (!price.isFinite() || price.lte(0)) {
|
|
990
|
+
throw new Error("Selected borrow token has an invalid price for deleverage sizing.");
|
|
991
|
+
}
|
|
992
|
+
const requiredDebtAssets = FormatConverter_1.default.decimalToBigInt(requiredDebtReductionUsd.div(price), borrow.asset.decimals);
|
|
993
|
+
if (requiredDebtAssets > selectedDebtAssets) {
|
|
994
|
+
throw new Error("Selected borrow token debt is too small for the requested deleverage target.");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
computePostDepositNaturalLeverage(currentCollateralInUsd, currentDebtInUsd, depositInUsd) {
|
|
998
|
+
if (currentDebtInUsd.lte(0))
|
|
999
|
+
return (0, decimal_js_1.default)(1);
|
|
1000
|
+
const collateralAfterDeposit = currentCollateralInUsd.add(depositInUsd);
|
|
1001
|
+
const equityAfterDeposit = collateralAfterDeposit.sub(currentDebtInUsd);
|
|
1002
|
+
if (equityAfterDeposit.lte(0))
|
|
1003
|
+
return (0, decimal_js_1.default)(1);
|
|
1004
|
+
return collateralAfterDeposit.div(equityAfterDeposit);
|
|
1005
|
+
}
|
|
1006
|
+
resolveLeverageUpPreview({ operation, targetLeverage, borrow, depositAssets = 0n, positionManagerType, }) {
|
|
1007
|
+
const leverageState = this.getMarketLeverageState();
|
|
1008
|
+
const currentLeverage = leverageState.currentLeverage ?? (0, decimal_js_1.default)(1);
|
|
1009
|
+
const currentCollateralInUsd = leverageState.currentCollateralInUsd;
|
|
1010
|
+
const depositInAssets = FormatConverter_1.default.bigIntToDecimal(depositAssets, this.asset.decimals);
|
|
1011
|
+
const depositInUsd = depositAssets > 0n
|
|
1012
|
+
? this.convertTokensToUsd(depositAssets, true)
|
|
1013
|
+
: (0, decimal_js_1.default)(0);
|
|
1014
|
+
const currentDebt = leverageState.currentDebt;
|
|
1015
|
+
const effectiveCurrentLeverage = depositAssets > 0n
|
|
1016
|
+
? this.computePostDepositNaturalLeverage(currentCollateralInUsd, currentDebt, depositInUsd)
|
|
1017
|
+
: currentLeverage;
|
|
1018
|
+
const cappedTargetLeverage = targetLeverage.gt(this.maxLeverage)
|
|
1019
|
+
? this.maxLeverage
|
|
1020
|
+
: targetLeverage;
|
|
1021
|
+
const resolvedTargetLeverage = operation === 'deposit-and-leverage'
|
|
1022
|
+
? decimal_js_1.default.max(cappedTargetLeverage, effectiveCurrentLeverage)
|
|
1023
|
+
: cappedTargetLeverage;
|
|
1024
|
+
if (operation === 'leverage-up' && resolvedTargetLeverage.lte(effectiveCurrentLeverage)) {
|
|
810
1025
|
throw new Error("New leverage must be more than current leverage");
|
|
811
1026
|
}
|
|
812
|
-
|
|
813
|
-
|
|
1027
|
+
const collateralAfterDepositInUsd = currentCollateralInUsd.add(depositInUsd);
|
|
1028
|
+
const notional = collateralAfterDepositInUsd.sub(currentDebt);
|
|
1029
|
+
if (notional.lte(0)) {
|
|
1030
|
+
throw new Error("Position has no positive equity to leverage.");
|
|
814
1031
|
}
|
|
815
|
-
const collateralAvail = this.cache.userCollateral + (depositAmount ? depositAmount : BigInt(0));
|
|
816
|
-
const collateralInUsd = this.convertTokensToUsd(collateralAvail, false);
|
|
817
|
-
const currentDebt = this.market.userDebt;
|
|
818
|
-
const notional = collateralInUsd.sub(currentDebt);
|
|
819
|
-
const leverageFactor = newLeverage.sub(1);
|
|
820
1032
|
const borrowPrice = borrow.getPrice(true);
|
|
821
|
-
const rawDebtInUsd = notional.mul(
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1033
|
+
const rawDebtInUsd = notional.mul(resolvedTargetLeverage).sub(notional);
|
|
1034
|
+
const debtIncrease = decimal_js_1.default.max(rawDebtInUsd.sub(currentDebt), (0, decimal_js_1.default)(0));
|
|
1035
|
+
const borrowAmount = borrowPrice.gt(0)
|
|
1036
|
+
? debtIncrease.div(borrowPrice)
|
|
1037
|
+
: (0, decimal_js_1.default)(0);
|
|
1038
|
+
const borrowAssets = debtIncrease.gt(0)
|
|
1039
|
+
? FormatConverter_1.default.decimalToBigInt(borrowAmount, borrow.asset.decimals)
|
|
1040
|
+
: 0n;
|
|
1041
|
+
const feePolicyCurrentLeverage = operation === 'deposit-and-leverage'
|
|
1042
|
+
? effectiveCurrentLeverage
|
|
1043
|
+
: currentLeverage;
|
|
1044
|
+
const hasSwapFee = positionManagerType !== 'vault' && positionManagerType !== 'native-vault';
|
|
1045
|
+
const feeBps = borrowAssets > 0n && hasSwapFee
|
|
1046
|
+
? this.setup.feePolicy.getFeeBps({
|
|
1047
|
+
operation,
|
|
1048
|
+
inputToken: borrow.asset.address,
|
|
1049
|
+
outputToken: this.asset.address,
|
|
1050
|
+
inputAmount: borrowAssets,
|
|
1051
|
+
currentLeverage: feePolicyCurrentLeverage,
|
|
1052
|
+
targetLeverage: resolvedTargetLeverage,
|
|
1053
|
+
})
|
|
1054
|
+
: 0n;
|
|
833
1055
|
const feeAssets = borrowAmount.mul((0, decimal_js_1.default)(Number(feeBps))).div((0, decimal_js_1.default)(10000));
|
|
834
1056
|
const feeUsd = feeAssets.mul(borrowPrice);
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
const
|
|
1057
|
+
const collateralIncreaseFromBorrow = decimal_js_1.default.max(debtIncrease.sub(feeUsd), (0, decimal_js_1.default)(0));
|
|
1058
|
+
const collateralIncrease = depositInUsd.add(collateralIncreaseFromBorrow);
|
|
1059
|
+
const collateralIncreaseInAssets = depositInAssets.add(this.convertUsdToTokens(collateralIncreaseFromBorrow, true));
|
|
1060
|
+
const newCollateralInUsd = currentCollateralInUsd.add(collateralIncrease);
|
|
838
1061
|
return {
|
|
1062
|
+
currentLeverage,
|
|
1063
|
+
effectiveCurrentLeverage,
|
|
1064
|
+
targetLeverage: resolvedTargetLeverage,
|
|
839
1065
|
borrowAmount,
|
|
1066
|
+
borrowAssets,
|
|
1067
|
+
debtIncrease,
|
|
1068
|
+
debtIncreaseInAssets: borrowAmount,
|
|
840
1069
|
newDebt: rawDebtInUsd,
|
|
841
1070
|
newDebtInAssets: borrow.convertUsdToTokens(rawDebtInUsd, true),
|
|
1071
|
+
collateralIncrease,
|
|
1072
|
+
collateralIncreaseInAssets,
|
|
842
1073
|
newCollateral: newCollateralInUsd,
|
|
843
1074
|
newCollateralInAssets: this.convertUsdToTokens(newCollateralInUsd, true),
|
|
844
1075
|
feeBps,
|
|
@@ -846,6 +1077,26 @@ class CToken extends Calldata_1.Calldata {
|
|
|
846
1077
|
feeUsd,
|
|
847
1078
|
};
|
|
848
1079
|
}
|
|
1080
|
+
previewDepositAndLeverage(newLeverage, borrow, depositAmount, positionManagerType) {
|
|
1081
|
+
return this.resolveLeverageUpPreview({
|
|
1082
|
+
operation: 'deposit-and-leverage',
|
|
1083
|
+
targetLeverage: newLeverage,
|
|
1084
|
+
borrow,
|
|
1085
|
+
depositAssets: depositAmount,
|
|
1086
|
+
positionManagerType,
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
previewLeverageUp(newLeverage, borrow, depositAmount, positionManagerType) {
|
|
1090
|
+
if ((depositAmount ?? 0n) > 0n) {
|
|
1091
|
+
return this.previewDepositAndLeverage(newLeverage, borrow, depositAmount, positionManagerType);
|
|
1092
|
+
}
|
|
1093
|
+
return this.resolveLeverageUpPreview({
|
|
1094
|
+
operation: 'leverage-up',
|
|
1095
|
+
targetLeverage: newLeverage,
|
|
1096
|
+
borrow,
|
|
1097
|
+
positionManagerType,
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
849
1100
|
previewLeverageDown(newLeverage, currentLeverage, borrow) {
|
|
850
1101
|
if (newLeverage.gte(currentLeverage)) {
|
|
851
1102
|
throw new Error("New leverage must be less than current leverage");
|
|
@@ -853,10 +1104,13 @@ class CToken extends Calldata_1.Calldata {
|
|
|
853
1104
|
if (newLeverage.lt((0, decimal_js_1.default)(1))) {
|
|
854
1105
|
throw new Error("New leverage must be at least 1");
|
|
855
1106
|
}
|
|
856
|
-
const
|
|
857
|
-
const collateralInUsd =
|
|
858
|
-
const currentDebt =
|
|
1107
|
+
const leverageState = this.getMarketLeverageState();
|
|
1108
|
+
const collateralInUsd = leverageState.currentCollateralInUsd;
|
|
1109
|
+
const currentDebt = leverageState.currentDebt;
|
|
859
1110
|
const equity = collateralInUsd.sub(currentDebt);
|
|
1111
|
+
if (equity.lte(0)) {
|
|
1112
|
+
throw new Error("Position has no positive equity to deleverage.");
|
|
1113
|
+
}
|
|
860
1114
|
const targetCollateralUsd = equity.mul(newLeverage);
|
|
861
1115
|
const newDebtUsd = targetCollateralUsd.sub(equity);
|
|
862
1116
|
const collateralAssetReductionUsd = collateralInUsd.sub(targetCollateralUsd);
|
|
@@ -897,22 +1151,30 @@ class CToken extends Calldata_1.Calldata {
|
|
|
897
1151
|
async leverageUp(borrow, newLeverage, type, slippage_ = (0, decimal_js_1.default)(0.05), simulate = false) {
|
|
898
1152
|
try {
|
|
899
1153
|
this.requireSigner();
|
|
900
|
-
const slippage = this._leverageUpSlippage(FormatConverter_1.default.percentageToBps(slippage_), newLeverage);
|
|
901
1154
|
const manager = this.getPositionManager(type);
|
|
1155
|
+
if (type === 'vault' || type === 'native-vault') {
|
|
1156
|
+
await this.assertVaultLeverageBorrowAssetSupported(borrow, type);
|
|
1157
|
+
}
|
|
1158
|
+
else if (type === 'simple') {
|
|
1159
|
+
this.assertSimpleLeverageSwapAssetsDiffer(borrow);
|
|
1160
|
+
}
|
|
902
1161
|
let calldata;
|
|
903
1162
|
await this._getLeverageSnapshot(borrow);
|
|
904
|
-
const
|
|
1163
|
+
const preview = this.previewLeverageUp(newLeverage, borrow, undefined, type);
|
|
1164
|
+
const slippage = this._leverageUpSlippage(FormatConverter_1.default.percentageToBps(slippage_), preview.targetLeverage);
|
|
1165
|
+
const { borrowAmount, borrowAssets, feeBps, targetLeverage } = preview;
|
|
1166
|
+
if (borrowAssets === 0n) {
|
|
1167
|
+
if (simulate) {
|
|
1168
|
+
return {
|
|
1169
|
+
success: false,
|
|
1170
|
+
error: "Target leverage must exceed the current leverage enough to borrow more.",
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
throw new Error("Target leverage must exceed the current leverage enough to borrow more.");
|
|
1174
|
+
}
|
|
1175
|
+
await this.assertLeverageBorrowCapacity(borrow, borrowAssets);
|
|
905
1176
|
switch (type) {
|
|
906
1177
|
case 'simple': {
|
|
907
|
-
const borrowAssets = FormatConverter_1.default.decimalToBigInt(borrowAmount, borrow.asset.decimals);
|
|
908
|
-
const feeBps = this.setup.feePolicy.getFeeBps({
|
|
909
|
-
operation: 'leverage-up',
|
|
910
|
-
inputToken: borrow.asset.address,
|
|
911
|
-
outputToken: this.asset.address,
|
|
912
|
-
inputAmount: borrowAssets,
|
|
913
|
-
currentLeverage: this.getLeverage() ?? (0, decimal_js_1.default)(1),
|
|
914
|
-
targetLeverage: newLeverage,
|
|
915
|
-
});
|
|
916
1178
|
const feeReceiver = feeBps > 0n ? this.setup.feePolicy.feeReceiver : undefined;
|
|
917
1179
|
const { action, quote } = await this.currentChainConfig.dexAgg.quoteAction(manager.address, borrow.asset.address, this.asset.address, borrowAssets, slippage, feeBps, feeReceiver);
|
|
918
1180
|
// Fee-aware slippage expansion now lives inside KyberSwap.quoteAction
|
|
@@ -923,10 +1185,10 @@ class CToken extends Calldata_1.Calldata {
|
|
|
923
1185
|
// as equity loss amplified by (L-1) — same pattern as
|
|
924
1186
|
// deleverage. Expand the contract-level tolerance to absorb it.
|
|
925
1187
|
// See `amplifyContractSlippage` in helpers.ts for rationale.
|
|
926
|
-
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage,
|
|
1188
|
+
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage, targetLeverage.sub(1), feeBps);
|
|
927
1189
|
calldata = manager.getLeverageCalldata({
|
|
928
1190
|
borrowableCToken: borrow.address,
|
|
929
|
-
borrowAssets:
|
|
1191
|
+
borrowAssets: borrowAssets,
|
|
930
1192
|
cToken: this.address,
|
|
931
1193
|
expectedShares: this.virtualConvertToShares(BigInt(quote.min_out), exports.LEVERAGE.SHARES_BUFFER_BPS),
|
|
932
1194
|
swapAction: action,
|
|
@@ -941,10 +1203,10 @@ class CToken extends Calldata_1.Calldata {
|
|
|
941
1203
|
// collateral drift between the vault's fundamental mint
|
|
942
1204
|
// rate at tx time and the stored oracle price that
|
|
943
1205
|
// `checkSlippage` reads. See LEVERAGE_UP_VAULT_DRIFT_BPS.
|
|
944
|
-
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage,
|
|
1206
|
+
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage, targetLeverage.sub(1), exports.LEVERAGE.LEVERAGE_UP_VAULT_DRIFT_BPS);
|
|
945
1207
|
calldata = manager.getLeverageCalldata({
|
|
946
1208
|
borrowableCToken: borrow.address,
|
|
947
|
-
borrowAssets:
|
|
1209
|
+
borrowAssets: borrowAssets,
|
|
948
1210
|
cToken: this.address,
|
|
949
1211
|
expectedShares: await PositionManager_1.PositionManager.getVaultExpectedShares(this, borrow, borrowAmount),
|
|
950
1212
|
swapAction: PositionManager_1.PositionManager.emptySwapAction(),
|
|
@@ -959,7 +1221,6 @@ class CToken extends Calldata_1.Calldata {
|
|
|
959
1221
|
}
|
|
960
1222
|
if (simulate)
|
|
961
1223
|
return this.simulateOracleRoute(calldata, { to: manager.address });
|
|
962
|
-
await this._checkPositionManagerApproval(manager);
|
|
963
1224
|
return this.oracleRoute(calldata, { to: manager.address });
|
|
964
1225
|
}
|
|
965
1226
|
catch (error) {
|
|
@@ -979,10 +1240,15 @@ class CToken extends Calldata_1.Calldata {
|
|
|
979
1240
|
const config = this.currentChainConfig;
|
|
980
1241
|
const slippage = (0, helpers_1.toBps)(slippage_);
|
|
981
1242
|
const manager = this.getPositionManager(type);
|
|
1243
|
+
if (type === 'simple') {
|
|
1244
|
+
this.assertSimpleLeverageSwapAssetsDiffer(borrowToken);
|
|
1245
|
+
}
|
|
982
1246
|
let calldata;
|
|
983
1247
|
const snapshot = await this._getLeverageSnapshot(borrowToken);
|
|
984
|
-
const
|
|
1248
|
+
const preview = this.previewLeverageDown(newLeverage, currentLeverage);
|
|
1249
|
+
const { collateralAssetReduction } = preview;
|
|
985
1250
|
const isFullDeleverage = newLeverage.equals(1);
|
|
1251
|
+
const maxTokenCollateral = this.virtualConvertToAssets(this.readFreshUserCache("userCollateral", "executing leverage down"));
|
|
986
1252
|
switch (type) {
|
|
987
1253
|
case 'simple': {
|
|
988
1254
|
let swapCollateral = collateralAssetReduction;
|
|
@@ -1025,18 +1291,31 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1025
1291
|
// fee+overhead magnitudes (< 100 bps combined).
|
|
1026
1292
|
const overheadBps = exports.LEVERAGE.DELEVERAGE_OVERHEAD_BPS + feeBps;
|
|
1027
1293
|
swapCollateral = debtInCollateral * (10000n + overheadBps) / 10000n;
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1294
|
+
if (swapCollateral > maxTokenCollateral) {
|
|
1295
|
+
const error = "Selected collateral token does not have enough posted collateral to fully deleverage.";
|
|
1296
|
+
if (simulate) {
|
|
1297
|
+
return { success: false, error };
|
|
1298
|
+
}
|
|
1299
|
+
throw new Error(error);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
this.assertSelectedBorrowDebtCanDeleverage(borrowToken, snapshot.debtTokenBalance, decimal_js_1.default.max(this.market.userDebt.sub(preview.newDebt), (0, decimal_js_1.default)(0)));
|
|
1304
|
+
if (feeBps > 0n) {
|
|
1305
|
+
// Partial deleverage: inflate swap size to compensate
|
|
1306
|
+
// for fee deduction on input. KyberSwap deducts feeBps
|
|
1307
|
+
// from input before swapping, so without compensation
|
|
1308
|
+
// the swap underdelivers and actual leverage is slightly
|
|
1309
|
+
// higher than target.
|
|
1310
|
+
swapCollateral = swapCollateral * 10000n / (10000n - feeBps);
|
|
1031
1311
|
}
|
|
1032
1312
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
swapCollateral = swapCollateral * 10000n / (10000n - feeBps);
|
|
1313
|
+
if (!isFullDeleverage && swapCollateral > maxTokenCollateral) {
|
|
1314
|
+
const error = "Selected collateral token does not have enough posted collateral to reach the requested leverage target.";
|
|
1315
|
+
if (simulate) {
|
|
1316
|
+
return { success: false, error };
|
|
1317
|
+
}
|
|
1318
|
+
throw new Error(error);
|
|
1040
1319
|
}
|
|
1041
1320
|
const { action, quote } = await config.dexAgg.quoteAction(manager.address, this.asset.address, borrowToken.asset.address, swapCollateral, slippage, feeBps, feeReceiver);
|
|
1042
1321
|
// Fee-aware slippage expansion for `_swapSafe` is handled by
|
|
@@ -1078,7 +1357,6 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1078
1357
|
}
|
|
1079
1358
|
if (simulate)
|
|
1080
1359
|
return this.simulateOracleRoute(calldata, { to: manager.address });
|
|
1081
|
-
await this._checkPositionManagerApproval(manager);
|
|
1082
1360
|
return this.oracleRoute(calldata, { to: manager.address });
|
|
1083
1361
|
}
|
|
1084
1362
|
catch (error) {
|
|
@@ -1095,30 +1373,39 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1095
1373
|
throw new Error("Multiplier must be greater than 1");
|
|
1096
1374
|
}
|
|
1097
1375
|
depositAmount = await this.ensureUnderlyingAmount(depositAmount, 'none');
|
|
1098
|
-
const slippage = this._leverageUpSlippage((0, helpers_1.toBps)(slippage_), multiplier);
|
|
1099
1376
|
const manager = this.getPositionManager(type);
|
|
1377
|
+
if (type === 'vault' || type === 'native-vault') {
|
|
1378
|
+
await this.assertVaultLeverageBorrowAssetSupported(borrow, type);
|
|
1379
|
+
}
|
|
1380
|
+
else if (type === 'simple') {
|
|
1381
|
+
this.assertSimpleLeverageSwapAssetsDiffer(borrow);
|
|
1382
|
+
}
|
|
1100
1383
|
let calldata;
|
|
1101
1384
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(depositAmount, this.asset.decimals);
|
|
1385
|
+
await this._checkTokenApproval(this.getPositionManagerDepositApprovalTarget(manager), depositAssets);
|
|
1102
1386
|
await this._getLeverageSnapshot(borrow);
|
|
1103
|
-
const
|
|
1387
|
+
const preview = this.previewDepositAndLeverage(multiplier, borrow, depositAssets, type);
|
|
1388
|
+
if (preview.borrowAssets === 0n) {
|
|
1389
|
+
if (simulate) {
|
|
1390
|
+
return {
|
|
1391
|
+
success: false,
|
|
1392
|
+
error: "Target leverage must exceed the post-deposit leverage to borrow more.",
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
throw new Error("Target leverage must exceed the post-deposit leverage to borrow more.");
|
|
1396
|
+
}
|
|
1397
|
+
const slippage = this._leverageUpSlippage((0, helpers_1.toBps)(slippage_), preview.targetLeverage);
|
|
1398
|
+
const { borrowAmount, borrowAssets, feeBps, targetLeverage } = preview;
|
|
1399
|
+
await this.assertLeverageBorrowCapacity(borrow, borrowAssets);
|
|
1104
1400
|
switch (type) {
|
|
1105
1401
|
case 'simple': {
|
|
1106
|
-
const borrowAssets = FormatConverter_1.default.decimalToBigInt(borrowAmount, borrow.asset.decimals);
|
|
1107
|
-
const feeBps = this.setup.feePolicy.getFeeBps({
|
|
1108
|
-
operation: 'deposit-and-leverage',
|
|
1109
|
-
inputToken: borrow.asset.address,
|
|
1110
|
-
outputToken: this.asset.address,
|
|
1111
|
-
inputAmount: borrowAssets,
|
|
1112
|
-
currentLeverage: this.getLeverage() ?? (0, decimal_js_1.default)(1),
|
|
1113
|
-
targetLeverage: multiplier,
|
|
1114
|
-
});
|
|
1115
1402
|
const feeReceiver = feeBps > 0n ? this.setup.feePolicy.feeReceiver : undefined;
|
|
1116
1403
|
const { action, quote } = await this.currentChainConfig.dexAgg.quoteAction(manager.address, borrow.asset.address, this.asset.address, borrowAssets, slippage, feeBps, feeReceiver);
|
|
1117
1404
|
// Fee-aware slippage expansion for `_swapSafe` is handled by
|
|
1118
1405
|
// KyberSwap.quoteAction. See KyberSwap.ts for rationale.
|
|
1119
1406
|
// Fee amplification: same pattern as leverageUp. See
|
|
1120
1407
|
// `amplifyContractSlippage` in helpers.ts.
|
|
1121
|
-
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage,
|
|
1408
|
+
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage, targetLeverage.sub(1), feeBps);
|
|
1122
1409
|
calldata = manager.getDepositAndLeverageCalldata(FormatConverter_1.default.decimalToBigInt(depositAmount, this.asset.decimals), {
|
|
1123
1410
|
borrowableCToken: borrow.address,
|
|
1124
1411
|
borrowAssets: borrowAssets,
|
|
@@ -1136,10 +1423,10 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1136
1423
|
// drift. Uses `multiplier.sub(1)` per the per-call-site
|
|
1137
1424
|
// asymmetry documented in helpers.ts (depositAndLeverage
|
|
1138
1425
|
// leverageDelta = multiplier - 1).
|
|
1139
|
-
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage,
|
|
1426
|
+
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage, targetLeverage.sub(1), exports.LEVERAGE.LEVERAGE_UP_VAULT_DRIFT_BPS);
|
|
1140
1427
|
calldata = manager.getDepositAndLeverageCalldata(FormatConverter_1.default.decimalToBigInt(depositAmount, this.asset.decimals), {
|
|
1141
1428
|
borrowableCToken: borrow.address,
|
|
1142
|
-
borrowAssets:
|
|
1429
|
+
borrowAssets: borrowAssets,
|
|
1143
1430
|
cToken: this.address,
|
|
1144
1431
|
expectedShares: await PositionManager_1.PositionManager.getVaultExpectedShares(this, borrow, borrowAmount),
|
|
1145
1432
|
swapAction: PositionManager_1.PositionManager.emptySwapAction(),
|
|
@@ -1154,7 +1441,6 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1154
1441
|
}
|
|
1155
1442
|
if (simulate)
|
|
1156
1443
|
return this.simulateOracleRoute(calldata, { to: manager.address });
|
|
1157
|
-
await this._checkPositionManagerApproval(manager);
|
|
1158
1444
|
return this.oracleRoute(calldata, { to: manager.address });
|
|
1159
1445
|
}
|
|
1160
1446
|
catch (error) {
|
|
@@ -1168,19 +1454,10 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1168
1454
|
amount = await this.ensureUnderlyingAmount(amount, zap);
|
|
1169
1455
|
const signer = this.requireSigner();
|
|
1170
1456
|
receiver ??= signer.address;
|
|
1171
|
-
const isZapping = typeof zap === 'object' && zap.type !== 'none';
|
|
1172
1457
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1173
|
-
|
|
1174
|
-
if (isZapping && zap.inputToken) {
|
|
1175
|
-
const isNative = zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase();
|
|
1176
|
-
const zapDecimals = isNative ? 18n : (() => {
|
|
1177
|
-
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, undefined, this.signer);
|
|
1178
|
-
return inputErc20.decimals ?? inputErc20.contract.decimals();
|
|
1179
|
-
})();
|
|
1180
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, await zapDecimals);
|
|
1181
|
-
}
|
|
1458
|
+
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1182
1459
|
const default_calldata = this.getCallData("deposit", [depositAssets, receiver]);
|
|
1183
|
-
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, false, default_calldata);
|
|
1460
|
+
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, false, default_calldata, receiver);
|
|
1184
1461
|
return this.simulateOracleRoute(calldata, calldata_overrides);
|
|
1185
1462
|
}
|
|
1186
1463
|
catch (error) {
|
|
@@ -1192,33 +1469,27 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1192
1469
|
amount = await this.ensureUnderlyingAmount(amount, zap);
|
|
1193
1470
|
const signer = this.requireSigner();
|
|
1194
1471
|
receiver ??= signer.address;
|
|
1195
|
-
const isZapping = typeof zap === 'object' && zap.type !== 'none';
|
|
1196
1472
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
})();
|
|
1204
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, await zapDecimals);
|
|
1205
|
-
}
|
|
1206
|
-
const default_calldata = this.getCallData("depositAsCollateral", [depositAssets, receiver]);
|
|
1207
|
-
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, true, default_calldata);
|
|
1473
|
+
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1474
|
+
const collateralMethod = receiver.toLowerCase() === signer.address.toLowerCase()
|
|
1475
|
+
? "depositAsCollateral"
|
|
1476
|
+
: "depositAsCollateralFor";
|
|
1477
|
+
const default_calldata = this.getCallData(collateralMethod, [depositAssets, receiver]);
|
|
1478
|
+
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, true, default_calldata, receiver);
|
|
1208
1479
|
return this.simulateOracleRoute(calldata, calldata_overrides);
|
|
1209
1480
|
}
|
|
1210
1481
|
catch (error) {
|
|
1211
1482
|
return { success: false, error: error?.reason || error?.message || String(error) };
|
|
1212
1483
|
}
|
|
1213
1484
|
}
|
|
1214
|
-
async zap(assets, zap, collateralize = false, default_calldata) {
|
|
1485
|
+
async zap(assets, zap, collateralize = false, default_calldata, receiver = this.requireSigner().address) {
|
|
1215
1486
|
let calldata;
|
|
1216
1487
|
let calldata_overrides = {};
|
|
1217
1488
|
let slippage = 0n;
|
|
1218
1489
|
let inputToken = null;
|
|
1219
1490
|
let type_of_zap;
|
|
1220
1491
|
if (typeof zap == 'object') {
|
|
1221
|
-
slippage =
|
|
1492
|
+
slippage = FormatConverter_1.default.percentageToBps(zap.slippage);
|
|
1222
1493
|
inputToken = zap.inputToken;
|
|
1223
1494
|
type_of_zap = zap.type;
|
|
1224
1495
|
}
|
|
@@ -1236,20 +1507,20 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1236
1507
|
case 'simple':
|
|
1237
1508
|
if (inputToken == null)
|
|
1238
1509
|
throw new Error("Input token must be provided for simple zap");
|
|
1239
|
-
calldata = await zapper.getSimpleZapCalldata(this, inputToken, this.asset.address, assets, collateralize, slippage);
|
|
1510
|
+
calldata = await zapper.getSimpleZapCalldata(this, inputToken, this.asset.address, assets, collateralize, slippage, receiver);
|
|
1240
1511
|
const isNativeSimpleZap = inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase();
|
|
1241
1512
|
calldata_overrides = isNativeSimpleZap ? { value: assets, to: zapper.address } : { to: zapper.address };
|
|
1242
1513
|
break;
|
|
1243
1514
|
case 'vault':
|
|
1244
|
-
calldata = await zapper.getVaultZapCalldata(this, assets, collateralize);
|
|
1515
|
+
calldata = await zapper.getVaultZapCalldata(this, assets, collateralize, false, receiver);
|
|
1245
1516
|
calldata_overrides = { to: zapper.address };
|
|
1246
1517
|
break;
|
|
1247
1518
|
case 'native-vault':
|
|
1248
|
-
calldata = await zapper.getNativeZapCalldata(this, assets, collateralize);
|
|
1519
|
+
calldata = await zapper.getNativeZapCalldata(this, assets, collateralize, false, receiver);
|
|
1249
1520
|
calldata_overrides = { value: assets, to: zapper.address };
|
|
1250
1521
|
break;
|
|
1251
1522
|
case 'native-simple':
|
|
1252
|
-
calldata = await zapper.getNativeZapCalldata(this, assets, collateralize, true);
|
|
1523
|
+
calldata = await zapper.getNativeZapCalldata(this, assets, collateralize, true, receiver);
|
|
1253
1524
|
calldata_overrides = { value: assets, to: zapper.address };
|
|
1254
1525
|
break;
|
|
1255
1526
|
default:
|
|
@@ -1261,84 +1532,50 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1261
1532
|
amount = await this.ensureUnderlyingAmount(amount, zap);
|
|
1262
1533
|
const signer = this.requireSigner();
|
|
1263
1534
|
receiver ??= signer.address;
|
|
1264
|
-
// When zapping, the swap amount uses input token decimals, but the
|
|
1265
|
-
// default deposit calldata uses the deposit token decimals.
|
|
1266
|
-
const isZapping = typeof zap === 'object' && zap.type !== 'none';
|
|
1267
1535
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
if (zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
1271
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, 18n);
|
|
1272
|
-
}
|
|
1273
|
-
else {
|
|
1274
|
-
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, undefined, this.signer);
|
|
1275
|
-
const zapDecimals = inputErc20.decimals ?? await inputErc20.contract.decimals();
|
|
1276
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, zapDecimals);
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
const zapType = typeof zap == 'object' ? zap.type : zap;
|
|
1280
|
-
const isNative = zapType == 'native-simple' || zapType == 'native-vault' || zapType == 'none'
|
|
1281
|
-
|| (typeof zap == 'object' && zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase());
|
|
1536
|
+
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1537
|
+
await this._checkDepositApprovals(zap, depositAssets, zapAssets);
|
|
1282
1538
|
const default_calldata = this.getCallData("deposit", [depositAssets, receiver]);
|
|
1283
|
-
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, false, default_calldata);
|
|
1284
|
-
|
|
1285
|
-
await this._checkAssetApproval(depositAssets);
|
|
1286
|
-
}
|
|
1287
|
-
else {
|
|
1288
|
-
const zapper = this.getZapper(zapType);
|
|
1289
|
-
if (!zapper) {
|
|
1290
|
-
throw new Error(`No zapper contract found for type '${zapType}' on ${this.symbol}`);
|
|
1291
|
-
}
|
|
1292
|
-
await this._checkDepositApprovals(zapper, zapAssets);
|
|
1293
|
-
}
|
|
1294
|
-
return this.oracleRoute(calldata, calldata_overrides);
|
|
1539
|
+
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, false, default_calldata, receiver);
|
|
1540
|
+
return this.oracleRoute(calldata, calldata_overrides, receiver);
|
|
1295
1541
|
}
|
|
1296
1542
|
async depositAsCollateral(amount, zap = 'none', receiver = null) {
|
|
1297
1543
|
amount = await this.ensureUnderlyingAmount(amount, zap);
|
|
1298
1544
|
const signer = this.requireSigner();
|
|
1299
1545
|
receiver ??= signer.address;
|
|
1300
|
-
// When zapping, the swap amount uses input token decimals, but collateral
|
|
1301
|
-
// cap checks and the default deposit calldata use the deposit token decimals.
|
|
1302
|
-
const isZapping = typeof zap === 'object' && zap.type !== 'none';
|
|
1303
1546
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1304
|
-
|
|
1305
|
-
if (
|
|
1306
|
-
if (zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
1307
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, 18n);
|
|
1308
|
-
}
|
|
1309
|
-
else {
|
|
1310
|
-
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, undefined, this.signer);
|
|
1311
|
-
const zapDecimals = inputErc20.decimals ?? await inputErc20.contract.decimals();
|
|
1312
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, zapDecimals);
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
if (!isZapping) {
|
|
1547
|
+
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1548
|
+
if (!this.isZapInstruction(zap)) {
|
|
1316
1549
|
const collateralCapError = "There is not enough collateral left in this tokens collateral cap for this deposit.";
|
|
1317
1550
|
const remainingCollateral = this.getRemainingCollateral(false);
|
|
1318
|
-
if (remainingCollateral
|
|
1551
|
+
if (remainingCollateral <= 0n)
|
|
1319
1552
|
throw new Error(collateralCapError);
|
|
1320
1553
|
if (remainingCollateral > 0n) {
|
|
1321
|
-
const shares = this.virtualConvertToShares(depositAssets
|
|
1554
|
+
const shares = this.virtualConvertToShares(depositAssets);
|
|
1322
1555
|
if (shares > remainingCollateral) {
|
|
1323
1556
|
throw new Error(collateralCapError);
|
|
1324
1557
|
}
|
|
1325
1558
|
}
|
|
1326
1559
|
}
|
|
1327
|
-
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
|
|
1560
|
+
await this._checkDepositApprovals(zap, depositAssets, zapAssets, true, receiver);
|
|
1561
|
+
const collateralMethod = receiver.toLowerCase() === signer.address.toLowerCase()
|
|
1562
|
+
? "depositAsCollateral"
|
|
1563
|
+
: "depositAsCollateralFor";
|
|
1564
|
+
const default_calldata = this.getCallData(collateralMethod, [depositAssets, receiver]);
|
|
1565
|
+
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, true, default_calldata, receiver);
|
|
1566
|
+
return this.oracleRoute(calldata, calldata_overrides, receiver);
|
|
1331
1567
|
}
|
|
1332
1568
|
async redeem(amount) {
|
|
1333
1569
|
const signer = this.requireSigner();
|
|
1334
1570
|
const receiver = signer.address;
|
|
1335
1571
|
const owner = signer.address;
|
|
1336
|
-
const buffer = this.
|
|
1572
|
+
const buffer = this.getExecutionDebtBufferTime();
|
|
1337
1573
|
const balance_avail = await this.balanceOf(signer.address);
|
|
1338
1574
|
const max_shares = await this.maxRedemption(true, buffer);
|
|
1339
1575
|
const converted_shares = this.convertTokenInputToShares(amount);
|
|
1340
|
-
|
|
1341
|
-
|
|
1576
|
+
const maxExecutableShares = max_shares < balance_avail ? max_shares : balance_avail;
|
|
1577
|
+
let shares = maxExecutableShares < converted_shares ? maxExecutableShares : converted_shares;
|
|
1578
|
+
if (maxExecutableShares === balance_avail && balance_avail - shares <= 10n) {
|
|
1342
1579
|
shares = balance_avail;
|
|
1343
1580
|
}
|
|
1344
1581
|
const calldata = this.getCallData("redeem", [shares, receiver, owner]);
|
|
@@ -1361,6 +1598,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1361
1598
|
const snapshot = await this.contract.getSnapshot(account);
|
|
1362
1599
|
return {
|
|
1363
1600
|
asset: snapshot.asset,
|
|
1601
|
+
underlying: snapshot.underlying,
|
|
1364
1602
|
decimals: BigInt(snapshot.decimals),
|
|
1365
1603
|
isCollateral: snapshot.isCollateral,
|
|
1366
1604
|
collateralPosted: BigInt(snapshot.collateralPosted),
|
|
@@ -1394,19 +1632,49 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1394
1632
|
const decimals = this.asset.decimals ?? this.decimals;
|
|
1395
1633
|
return FormatConverter_1.default.bigIntTokensToUsd(tokenAmount, price, decimals);
|
|
1396
1634
|
}
|
|
1397
|
-
|
|
1398
|
-
tokenAmount = this.virtualConvertToShares(tokenAmount);
|
|
1635
|
+
convertSharesToUsdSync(tokenAmount) {
|
|
1399
1636
|
const price = this.getPrice(false, false, false);
|
|
1400
1637
|
const decimals = this.decimals;
|
|
1401
1638
|
return FormatConverter_1.default.bigIntTokensToUsd(tokenAmount, price, decimals);
|
|
1402
1639
|
}
|
|
1403
|
-
|
|
1640
|
+
async fetchConvertSharesToUsd(tokenAmount) {
|
|
1641
|
+
await this.fetchPrice(false);
|
|
1642
|
+
await this.fetchDecimals();
|
|
1643
|
+
return this.convertSharesToUsdSync(tokenAmount);
|
|
1644
|
+
}
|
|
1645
|
+
async convertSharesToUsd(tokenAmount) {
|
|
1646
|
+
return this.convertSharesToUsdSync(tokenAmount);
|
|
1647
|
+
}
|
|
1648
|
+
buildMultiCallAction(calldata, target = this.address) {
|
|
1404
1649
|
return {
|
|
1405
|
-
target
|
|
1650
|
+
target,
|
|
1406
1651
|
isPriceUpdate: false,
|
|
1407
1652
|
data: calldata
|
|
1408
1653
|
};
|
|
1409
1654
|
}
|
|
1655
|
+
hasNonZeroNativeValueOverride(override) {
|
|
1656
|
+
const value = override.value;
|
|
1657
|
+
if (value == null) {
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
if (typeof value === "bigint") {
|
|
1661
|
+
return value > 0n;
|
|
1662
|
+
}
|
|
1663
|
+
if (typeof value === "number") {
|
|
1664
|
+
return value > 0;
|
|
1665
|
+
}
|
|
1666
|
+
if (typeof value === "string") {
|
|
1667
|
+
return BigInt(value) > 0n;
|
|
1668
|
+
}
|
|
1669
|
+
return true;
|
|
1670
|
+
}
|
|
1671
|
+
assertOracleMulticallSupportsValue(priceUpdates, override) {
|
|
1672
|
+
if (priceUpdates.length === 0 || !this.hasNonZeroNativeValueOverride(override)) {
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
throw new Error("Native gas-token zaps cannot be combined with oracle price-update multicalls. " +
|
|
1676
|
+
"Use the wrapped-native zap path or retry when no oracle update is required.");
|
|
1677
|
+
}
|
|
1410
1678
|
async _checkPositionManagerApproval(manager) {
|
|
1411
1679
|
const isApproved = await this.isPluginApproved(manager.type, 'positionManager');
|
|
1412
1680
|
if (!isApproved) {
|
|
@@ -1414,71 +1682,172 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1414
1682
|
}
|
|
1415
1683
|
}
|
|
1416
1684
|
async _checkZapperApproval(zapper) {
|
|
1417
|
-
|
|
1418
|
-
|
|
1685
|
+
const plugin_allowed = await this.isPluginApproved(zapper.type, 'zapper');
|
|
1686
|
+
if (!plugin_allowed) {
|
|
1687
|
+
throw new Error(`Please approve the ${zapper.type} Zapper to be able to move ${this.symbol} on your behalf.`);
|
|
1419
1688
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}
|
|
1689
|
+
}
|
|
1690
|
+
async _checkDelegateApproval(owner, delegate, delegateLabel) {
|
|
1691
|
+
const pluginAllowed = await this.contract.isDelegate(owner, delegate);
|
|
1692
|
+
if (!pluginAllowed) {
|
|
1693
|
+
throw new Error(`Please approve ${delegateLabel} as a delegate for ${this.symbol} on behalf of ${owner}.`);
|
|
1425
1694
|
}
|
|
1426
1695
|
}
|
|
1427
|
-
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1696
|
+
getDepositAssetApprovalTarget() {
|
|
1697
|
+
const asset = this.getAsset(true);
|
|
1698
|
+
return {
|
|
1699
|
+
token: asset,
|
|
1700
|
+
spender: this.address,
|
|
1701
|
+
spenderLabel: this.symbol,
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
getPositionManagerDepositApprovalTarget(manager) {
|
|
1705
|
+
return {
|
|
1706
|
+
token: this.getAsset(true),
|
|
1707
|
+
spender: manager.address,
|
|
1708
|
+
spenderLabel: `${manager.type} PositionManager`,
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
async resolveZapApprovalTarget(instructions) {
|
|
1712
|
+
const zapType = typeof instructions == 'object' ? instructions.type : instructions;
|
|
1713
|
+
if (zapType == 'none') {
|
|
1714
|
+
return null;
|
|
1715
|
+
}
|
|
1716
|
+
const spender = this.getPluginAddress(zapType, 'zapper');
|
|
1717
|
+
if (spender == null) {
|
|
1718
|
+
throw new Error("Plugin does not have an associated contract");
|
|
1719
|
+
}
|
|
1720
|
+
switch (zapType) {
|
|
1721
|
+
case 'native-vault':
|
|
1722
|
+
case 'native-simple':
|
|
1723
|
+
return null;
|
|
1724
|
+
case 'vault':
|
|
1725
|
+
return {
|
|
1726
|
+
token: await this.getVaultAsset(true),
|
|
1727
|
+
spender,
|
|
1728
|
+
spenderLabel: `${zapType} Zapper`,
|
|
1729
|
+
};
|
|
1730
|
+
case 'simple':
|
|
1731
|
+
if (typeof instructions != 'object') {
|
|
1732
|
+
throw new Error("Input token must be provided for simple zap approval");
|
|
1733
|
+
}
|
|
1734
|
+
if (instructions.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
return {
|
|
1738
|
+
token: new ERC20_1.ERC20(this.provider, instructions.inputToken, undefined, undefined, this.signer),
|
|
1739
|
+
spender,
|
|
1740
|
+
spenderLabel: `${zapType} Zapper`,
|
|
1741
|
+
};
|
|
1434
1742
|
}
|
|
1435
1743
|
}
|
|
1436
|
-
async
|
|
1437
|
-
|
|
1744
|
+
async hasTokenApproval(target, amount) {
|
|
1745
|
+
const owner = this.getAccountOrThrow();
|
|
1746
|
+
const allowance = await target.token.allowance(owner, target.spender);
|
|
1747
|
+
return allowance >= amount;
|
|
1748
|
+
}
|
|
1749
|
+
async _checkTokenApproval(target, amount) {
|
|
1750
|
+
const allowance = await this.hasTokenApproval(target, amount);
|
|
1751
|
+
if (allowance) {
|
|
1438
1752
|
return;
|
|
1439
1753
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1754
|
+
let tokenLabel = target.token.symbol ?? target.token.address;
|
|
1755
|
+
if (target.token.symbol == undefined) {
|
|
1756
|
+
try {
|
|
1757
|
+
tokenLabel = await target.token.fetchSymbol();
|
|
1758
|
+
}
|
|
1759
|
+
catch {
|
|
1760
|
+
tokenLabel = target.token.address;
|
|
1761
|
+
}
|
|
1445
1762
|
}
|
|
1763
|
+
throw new Error(`Please approve the ${tokenLabel} token for ${target.spenderLabel}`);
|
|
1446
1764
|
}
|
|
1447
|
-
async _checkDepositApprovals(
|
|
1448
|
-
|
|
1449
|
-
|
|
1765
|
+
async _checkDepositApprovals(zap, depositAssets, zapAssets, collateralize = false, receiver = null) {
|
|
1766
|
+
const zapType = typeof zap == 'object' ? zap.type : zap;
|
|
1767
|
+
const signer = this.requireSigner();
|
|
1768
|
+
if (zapType != 'none') {
|
|
1769
|
+
const zapper = this.getZapper(zapType);
|
|
1770
|
+
if (!zapper) {
|
|
1771
|
+
throw new Error(`No zapper contract found for type '${zapType}' on ${this.symbol}`);
|
|
1772
|
+
}
|
|
1773
|
+
if (collateralize) {
|
|
1774
|
+
const receiverAddress = receiver ?? signer.address;
|
|
1775
|
+
if (receiverAddress.toLowerCase() === signer.address.toLowerCase()) {
|
|
1776
|
+
await this._checkZapperApproval(zapper);
|
|
1777
|
+
}
|
|
1778
|
+
else {
|
|
1779
|
+
await this._checkDelegateApproval(receiverAddress, signer.address, "the connected signer");
|
|
1780
|
+
await this._checkDelegateApproval(receiverAddress, zapper.address, `${zapper.type} Zapper`);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1450
1783
|
}
|
|
1451
|
-
if (
|
|
1452
|
-
await this.
|
|
1784
|
+
else if (collateralize && receiver && receiver.toLowerCase() !== signer.address.toLowerCase()) {
|
|
1785
|
+
await this._checkDelegateApproval(receiver, signer.address, "the connected signer");
|
|
1453
1786
|
}
|
|
1454
|
-
|
|
1787
|
+
const approvalTarget = zapType == 'none'
|
|
1788
|
+
? this.getDepositAssetApprovalTarget()
|
|
1789
|
+
: await this.resolveZapApprovalTarget(zap);
|
|
1790
|
+
if (approvalTarget == null) {
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
const approvalAmount = zapType == 'none' ? depositAssets : zapAssets;
|
|
1794
|
+
await this._checkTokenApproval(approvalTarget, approvalAmount);
|
|
1455
1795
|
}
|
|
1456
|
-
async oracleRoute(calldata, override = {}) {
|
|
1796
|
+
async oracleRoute(calldata, override = {}, reloadAccount = null) {
|
|
1457
1797
|
const signer = this.requireSigner();
|
|
1458
1798
|
const price_updates = await this.getPriceUpdates();
|
|
1799
|
+
this.assertOracleMulticallSupportsValue(price_updates, override);
|
|
1459
1800
|
if (price_updates.length > 0) {
|
|
1460
|
-
const
|
|
1801
|
+
const actionTarget = (override.to ?? this.address);
|
|
1802
|
+
const token_action = this.buildMultiCallAction(calldata, actionTarget);
|
|
1461
1803
|
calldata = this.getCallData("multicall", [[...price_updates, token_action]]);
|
|
1462
1804
|
}
|
|
1463
1805
|
const tx = await this.executeCallData(calldata, override);
|
|
1464
|
-
|
|
1806
|
+
if (typeof tx.wait === "function") {
|
|
1807
|
+
await tx.wait();
|
|
1808
|
+
}
|
|
1809
|
+
const refreshAccount = reloadAccount ?? signer.address;
|
|
1810
|
+
await this.market.reloadUserData(refreshAccount, {
|
|
1811
|
+
allowSignerMismatch: refreshAccount.toLowerCase() !== signer.address.toLowerCase(),
|
|
1812
|
+
});
|
|
1465
1813
|
return tx;
|
|
1466
1814
|
}
|
|
1467
1815
|
async simulateOracleRoute(calldata, override = {}) {
|
|
1468
1816
|
const price_updates = await this.getPriceUpdates();
|
|
1817
|
+
this.assertOracleMulticallSupportsValue(price_updates, override);
|
|
1469
1818
|
if (price_updates.length > 0) {
|
|
1470
|
-
const
|
|
1819
|
+
const actionTarget = (override.to ?? this.address);
|
|
1820
|
+
const token_action = this.buildMultiCallAction(calldata, actionTarget);
|
|
1471
1821
|
calldata = this.getCallData("multicall", [[...price_updates, token_action]]);
|
|
1472
1822
|
}
|
|
1473
1823
|
return this.simulateCallData(calldata, override);
|
|
1474
1824
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1825
|
+
getRedstonePriceUpdateTokens() {
|
|
1826
|
+
const candidates = (this.market?.tokens?.length ? this.market.tokens : [this]);
|
|
1827
|
+
const seenAssets = new Set();
|
|
1828
|
+
const tokens = [];
|
|
1829
|
+
for (const token of candidates) {
|
|
1830
|
+
if (!token.adapters?.includes(ProtocolReader_1.AdaptorTypes.REDSTONE_CORE)) {
|
|
1831
|
+
continue;
|
|
1832
|
+
}
|
|
1833
|
+
let assetAddress;
|
|
1834
|
+
try {
|
|
1835
|
+
assetAddress = token.getAsset(false);
|
|
1836
|
+
}
|
|
1837
|
+
catch {
|
|
1838
|
+
assetAddress = token.cache?.asset?.address ?? token.address;
|
|
1839
|
+
}
|
|
1840
|
+
const key = assetAddress.toLowerCase();
|
|
1841
|
+
if (seenAssets.has(key)) {
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
seenAssets.add(key);
|
|
1845
|
+
tokens.push(token);
|
|
1480
1846
|
}
|
|
1481
|
-
return
|
|
1847
|
+
return tokens;
|
|
1848
|
+
}
|
|
1849
|
+
async getPriceUpdates() {
|
|
1850
|
+
return Promise.all(this.getRedstonePriceUpdateTokens().map((token) => Redstone_1.Redstone.buildMultiCallAction(token)));
|
|
1482
1851
|
}
|
|
1483
1852
|
}
|
|
1484
1853
|
exports.CToken = CToken;
|