curvance 5.1.8 → 5.2.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 +251 -70
- package/dist/abis/OptimizerReader.json +292 -1
- package/dist/chains/arbitrum.d.ts.map +1 -1
- package/dist/chains/arbitrum.js +14 -1
- package/dist/chains/arbitrum.js.map +1 -1
- package/dist/chains/index.d.ts +19 -0
- package/dist/chains/index.d.ts.map +1 -1
- package/dist/chains/index.js.map +1 -1
- package/dist/chains/monad.d.ts.map +1 -1
- package/dist/chains/monad.js +25 -2
- package/dist/chains/monad.js.map +1 -1
- package/dist/chains/services.d.ts +8 -0
- package/dist/chains/services.d.ts.map +1 -0
- package/dist/chains/services.js +9 -0
- package/dist/chains/services.js.map +1 -0
- package/dist/classes/Api.d.ts +7 -2
- package/dist/classes/Api.d.ts.map +1 -1
- package/dist/classes/Api.js +60 -24
- package/dist/classes/Api.js.map +1 -1
- package/dist/classes/BorrowableCToken.d.ts.map +1 -1
- package/dist/classes/BorrowableCToken.js +7 -2
- package/dist/classes/BorrowableCToken.js.map +1 -1
- package/dist/classes/CToken.d.ts +36 -29
- package/dist/classes/CToken.d.ts.map +1 -1
- package/dist/classes/CToken.js +225 -166
- package/dist/classes/CToken.js.map +1 -1
- package/dist/classes/DexAggregators/IDexAgg.d.ts +8 -0
- package/dist/classes/DexAggregators/IDexAgg.d.ts.map +1 -1
- package/dist/classes/DexAggregators/KyberSwap.d.ts +5 -2
- package/dist/classes/DexAggregators/KyberSwap.d.ts.map +1 -1
- package/dist/classes/DexAggregators/KyberSwap.js +41 -19
- package/dist/classes/DexAggregators/KyberSwap.js.map +1 -1
- package/dist/classes/DexAggregators/MultiDexAgg.d.ts +7 -4
- package/dist/classes/DexAggregators/MultiDexAgg.d.ts.map +1 -1
- package/dist/classes/DexAggregators/MultiDexAgg.js +62 -16
- package/dist/classes/DexAggregators/MultiDexAgg.js.map +1 -1
- package/dist/classes/DexAggregators/helpers.d.ts +1 -1
- package/dist/classes/DexAggregators/helpers.d.ts.map +1 -1
- package/dist/classes/DexAggregators/helpers.js +1 -1
- package/dist/classes/DexAggregators/helpers.js.map +1 -1
- package/dist/classes/DexAggregators/index.d.ts +0 -1
- package/dist/classes/DexAggregators/index.d.ts.map +1 -1
- package/dist/classes/DexAggregators/index.js +0 -1
- package/dist/classes/DexAggregators/index.js.map +1 -1
- package/dist/classes/LendingOptimizer.d.ts +7 -0
- package/dist/classes/LendingOptimizer.d.ts.map +1 -1
- package/dist/classes/LendingOptimizer.js +12 -2
- package/dist/classes/LendingOptimizer.js.map +1 -1
- package/dist/classes/Market.d.ts +5 -0
- package/dist/classes/Market.d.ts.map +1 -1
- package/dist/classes/Market.js +129 -30
- package/dist/classes/Market.js.map +1 -1
- package/dist/classes/NativeToken.d.ts +5 -2
- package/dist/classes/NativeToken.d.ts.map +1 -1
- package/dist/classes/NativeToken.js +5 -5
- package/dist/classes/NativeToken.js.map +1 -1
- package/dist/classes/OptimizerReader.d.ts +44 -4
- package/dist/classes/OptimizerReader.d.ts.map +1 -1
- package/dist/classes/OptimizerReader.js +133 -62
- package/dist/classes/OptimizerReader.js.map +1 -1
- package/dist/classes/PositionManager.d.ts +1 -0
- package/dist/classes/PositionManager.d.ts.map +1 -1
- package/dist/classes/PositionManager.js +25 -0
- package/dist/classes/PositionManager.js.map +1 -1
- package/dist/classes/ProtocolReader.d.ts +3 -0
- package/dist/classes/ProtocolReader.d.ts.map +1 -1
- package/dist/classes/ProtocolReader.js +34 -0
- package/dist/classes/ProtocolReader.js.map +1 -1
- package/dist/classes/Zapper.d.ts +4 -1
- package/dist/classes/Zapper.d.ts.map +1 -1
- package/dist/classes/Zapper.js +34 -9
- package/dist/classes/Zapper.js.map +1 -1
- package/dist/classes/index.d.ts +1 -1
- package/dist/classes/index.d.ts.map +1 -1
- package/dist/classes/index.js +1 -1
- package/dist/classes/index.js.map +1 -1
- package/dist/contracts/index.d.ts +249 -245
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +3 -2
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/monad-mainnet.json +4 -0
- package/dist/feePolicy.d.ts +29 -26
- package/dist/feePolicy.d.ts.map +1 -1
- package/dist/feePolicy.js +43 -34
- package/dist/feePolicy.js.map +1 -1
- package/dist/format/index.d.ts +5 -0
- package/dist/format/index.d.ts.map +1 -1
- package/dist/format/index.js +28 -0
- package/dist/format/index.js.map +1 -1
- package/dist/helpers.d.ts +248 -245
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +5 -5
- package/dist/helpers.js.map +1 -1
- package/dist/immutability.d.ts +6 -0
- package/dist/immutability.d.ts.map +1 -0
- package/dist/immutability.js +25 -0
- package/dist/immutability.js.map +1 -0
- package/dist/integrations/merkl.d.ts +9 -2
- package/dist/integrations/merkl.d.ts.map +1 -1
- package/dist/integrations/merkl.js +219 -11
- package/dist/integrations/merkl.js.map +1 -1
- package/dist/integrations/snapshot.d.ts +3 -0
- package/dist/integrations/snapshot.d.ts.map +1 -1
- package/dist/integrations/snapshot.js +47 -3
- package/dist/integrations/snapshot.js.map +1 -1
- package/dist/setup.d.ts +13 -3
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +101 -7
- package/dist/setup.js.map +1 -1
- package/package.json +7 -5
- package/dist/classes/Redstone.d.ts +0 -15
- package/dist/classes/Redstone.d.ts.map +0 -1
- package/dist/classes/Redstone.js +0 -56
- package/dist/classes/Redstone.js.map +0 -1
package/dist/classes/CToken.js
CHANGED
|
@@ -10,15 +10,11 @@ const ERC20_1 = require("./ERC20");
|
|
|
10
10
|
const Calldata_1 = require("./Calldata");
|
|
11
11
|
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
12
12
|
const BaseCToken_json_1 = __importDefault(require("../abis/BaseCToken.json"));
|
|
13
|
-
const Redstone_1 = require("./Redstone");
|
|
14
13
|
const Zapper_1 = require("./Zapper");
|
|
15
14
|
const PositionManager_1 = require("./PositionManager");
|
|
16
15
|
const NativeToken_1 = require("./NativeToken");
|
|
17
16
|
const ERC4626_1 = require("./ERC4626");
|
|
18
17
|
const FormatConverter_1 = __importDefault(require("./FormatConverter"));
|
|
19
|
-
const EXCLUDED_ZAP_SYMBOLS = new Set([
|
|
20
|
-
'eBTC', 'vUSD', 'ezETH', 'YZM', 'wsrUSD', 'sAUSD',
|
|
21
|
-
]);
|
|
22
18
|
const EXECUTION_DEBT_BUFFER_TIME = 100n;
|
|
23
19
|
function ceilDiv(numerator, denominator) {
|
|
24
20
|
if (denominator <= 0n) {
|
|
@@ -33,9 +29,9 @@ function ceilDiv(numerator, denominator) {
|
|
|
33
29
|
*
|
|
34
30
|
* Single-oracle architecture (permanent design)
|
|
35
31
|
* ---------------------------------------------
|
|
36
|
-
* Curvance uses single-adaptor oracle configs only
|
|
37
|
-
*
|
|
38
|
-
*
|
|
32
|
+
* Curvance uses single-adaptor oracle configs only. The adaptor path ignores
|
|
33
|
+
* the getLower flag — see line 78 of BaseOracleAdaptor.sol. Dual-feed mode
|
|
34
|
+
* was deprecated in favor of the
|
|
39
35
|
* price-guard system and orderflow MEV tech, and is not coming back.
|
|
40
36
|
* This means MarketManager._statusOf returns symmetric prices for
|
|
41
37
|
* collateral (queries with getLower=true) and debt (getLower=false), so
|
|
@@ -63,7 +59,7 @@ function ceilDiv(numerator, denominator) {
|
|
|
63
59
|
* Leverage UP: under single-oracle, the contract sees zero forced loss
|
|
64
60
|
* for a perfect swap. The only real sources of difference between
|
|
65
61
|
* snapshot-time prices and execution-time prices are: (a) wei-level share
|
|
66
|
-
* rounding, (b)
|
|
62
|
+
* rounding, (b) oracle price drift between the snapshot RPC and the
|
|
67
63
|
* tx broadcast block. Both are small constants in absolute terms, NOT
|
|
68
64
|
* leverage-scaled. A small flat buffer suffices.
|
|
69
65
|
*
|
|
@@ -87,8 +83,7 @@ exports.LEVERAGE = {
|
|
|
87
83
|
* for the same market)
|
|
88
84
|
* - `CURVANCE_FEE_BPS` (deterministic, amplified by (L-1); at L=10
|
|
89
85
|
* eats ~36bps of equity-fraction)
|
|
90
|
-
* - Oracle drift between preview snapshot and
|
|
91
|
-
* tx inclusion
|
|
86
|
+
* - Oracle drift between preview snapshot and tx inclusion
|
|
92
87
|
* - Share rounding (wei-level)
|
|
93
88
|
*
|
|
94
89
|
* History: 0.99 → 0.995 when caching improved precision (pre-fee era).
|
|
@@ -102,7 +97,7 @@ exports.LEVERAGE = {
|
|
|
102
97
|
MAX_LEVERAGE_FACTOR: (0, decimal_js_1.default)(0.98),
|
|
103
98
|
/** Flat BPS buffer added to leverage-up DEX/swapSafe slippage tolerance.
|
|
104
99
|
* Under single-oracle, the only forced loss at the swap level comes from
|
|
105
|
-
* wei-level share rounding plus possible
|
|
100
|
+
* wei-level share rounding plus possible oracle price drift between
|
|
106
101
|
* snapshot RPC and tx broadcast block. Both are small constants.
|
|
107
102
|
*
|
|
108
103
|
* Fee handling: KyberSwap.quoteAction expands action.slippage by feeBps
|
|
@@ -184,27 +179,44 @@ class CToken extends Calldata_1.Calldata {
|
|
|
184
179
|
this.contract = (0, helpers_1.contractSetup)(this.provider, address, BaseCToken_json_1.default);
|
|
185
180
|
this.cache = cache;
|
|
186
181
|
this.market = market;
|
|
187
|
-
const chainSettings = this.
|
|
182
|
+
const chainSettings = this.currentChainAssets;
|
|
188
183
|
const assetAddr = this.asset.address.toLowerCase();
|
|
189
184
|
this.isNativeVault = chainSettings.native_vaults.some(vault => vault.contract.toLowerCase() == assetAddr);
|
|
190
185
|
this.isVault = chainSettings.vaults.some(vault => vault.contract.toLowerCase() == assetAddr);
|
|
191
186
|
this.isWrappedNative = chainSettings.wrapped_native.toLowerCase() == assetAddr;
|
|
192
|
-
|
|
187
|
+
this.refreshRouteCapabilities();
|
|
188
|
+
}
|
|
189
|
+
refreshRouteCapabilities() {
|
|
190
|
+
this.zapTypes = [];
|
|
191
|
+
this.leverageTypes = [];
|
|
192
|
+
if (this.isZapSymbolExcluded(this.asset.symbol)) {
|
|
193
193
|
return;
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
const zappers = this.setup.contracts.zappers;
|
|
196
|
+
const nativeVaultZapper = zappers?.nativeVaultZapper;
|
|
197
|
+
const vaultZapper = zappers?.vaultZapper;
|
|
198
|
+
const simpleZapper = zappers?.simpleZapper;
|
|
199
|
+
const supportsNativeVaultZaps = typeof nativeVaultZapper === 'string'
|
|
200
|
+
&& nativeVaultZapper.toLowerCase() !== helpers_1.EMPTY_ADDRESS.toLowerCase();
|
|
201
|
+
const supportsVaultZaps = typeof vaultZapper === 'string'
|
|
202
|
+
&& vaultZapper.toLowerCase() !== helpers_1.EMPTY_ADDRESS.toLowerCase();
|
|
203
|
+
const supportsSimpleZaps = typeof simpleZapper === 'string'
|
|
204
|
+
&& simpleZapper.toLowerCase() !== helpers_1.EMPTY_ADDRESS.toLowerCase()
|
|
205
|
+
&& this.hasExecutableDexRoute;
|
|
206
|
+
if (supportsNativeVaultZaps && this.isNativeVault)
|
|
196
207
|
this.zapTypes.push('native-vault');
|
|
197
208
|
if ("nativeVaultPositionManager" in this.market.plugins && this.isNativeVault)
|
|
198
209
|
this.leverageTypes.push('native-vault');
|
|
199
|
-
if (this.isWrappedNative)
|
|
210
|
+
if (supportsSimpleZaps && this.isWrappedNative)
|
|
200
211
|
this.zapTypes.push('native-simple');
|
|
201
|
-
if (this.isVault)
|
|
212
|
+
if (supportsVaultZaps && this.isVault)
|
|
202
213
|
this.zapTypes.push('vault');
|
|
203
214
|
if ("vaultPositionManager" in this.market.plugins && this.isVault)
|
|
204
215
|
this.leverageTypes.push('vault');
|
|
205
|
-
if ("simplePositionManager" in this.market.plugins)
|
|
216
|
+
if (supportsSimpleZaps && "simplePositionManager" in this.market.plugins)
|
|
206
217
|
this.leverageTypes.push('simple');
|
|
207
|
-
|
|
218
|
+
if (supportsSimpleZaps)
|
|
219
|
+
this.zapTypes.push('simple');
|
|
208
220
|
}
|
|
209
221
|
getUserCacheFreshness() {
|
|
210
222
|
if (this.userCacheFreshness == undefined) {
|
|
@@ -233,7 +245,28 @@ class CToken extends Calldata_1.Calldata {
|
|
|
233
245
|
}
|
|
234
246
|
get setup() { return this.market.setup; }
|
|
235
247
|
get currentChain() { return this.setup.chain; }
|
|
236
|
-
get
|
|
248
|
+
get currentChainAssets() { return this.setup.assets; }
|
|
249
|
+
isZapSymbolExcluded(symbol) {
|
|
250
|
+
if (symbol == null) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
const excludedSymbols = this.setup.assets
|
|
254
|
+
?.excluded_zap_symbols ?? [];
|
|
255
|
+
return excludedSymbols.some((excluded) => excluded.toLowerCase() === symbol.toLowerCase());
|
|
256
|
+
}
|
|
257
|
+
get boundDexAgg() { return this.market.dexAgg ?? null; }
|
|
258
|
+
get currentDexAgg() {
|
|
259
|
+
const dexAgg = this.boundDexAgg;
|
|
260
|
+
if (dexAgg == null) {
|
|
261
|
+
throw new Error(`DEX aggregator is not bound for token ${this.address} on ${this.currentChain}. ` +
|
|
262
|
+
`Use setupChain(...) result markets or attach a setup-bound dexAgg before route discovery/execution.`);
|
|
263
|
+
}
|
|
264
|
+
return dexAgg;
|
|
265
|
+
}
|
|
266
|
+
get hasExecutableDexRoute() {
|
|
267
|
+
const router = this.boundDexAgg?.router;
|
|
268
|
+
return typeof router === "string" && router.toLowerCase() !== helpers_1.EMPTY_ADDRESS.toLowerCase();
|
|
269
|
+
}
|
|
237
270
|
requireSigner() { return (0, helpers_1.requireSigner)(this.signer); }
|
|
238
271
|
getAccountOrThrow(account = null) {
|
|
239
272
|
return (0, helpers_1.requireAccount)(account ?? this.account, this.signer);
|
|
@@ -241,6 +274,31 @@ class CToken extends Calldata_1.Calldata {
|
|
|
241
274
|
getWriteContract() {
|
|
242
275
|
return (0, helpers_1.contractSetup)(this.requireSigner(), this.address, BaseCToken_json_1.default);
|
|
243
276
|
}
|
|
277
|
+
assertBorrowTokenBelongsToMarket(borrow, label = "Borrow") {
|
|
278
|
+
if (borrow == null) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const borrowMarket = borrow.market;
|
|
282
|
+
if (borrowMarket == undefined) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (borrowMarket === this.market) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const borrowChain = borrowMarket.setup?.chain;
|
|
289
|
+
const collateralChain = this.market.setup?.chain;
|
|
290
|
+
const sameMarket = borrowMarket.address?.toLowerCase() === this.market.address.toLowerCase();
|
|
291
|
+
const sameChain = borrowChain != null && collateralChain != null && borrowChain === collateralChain;
|
|
292
|
+
const borrowReaderKey = borrowMarket.reader?.batchKey ?? null;
|
|
293
|
+
const collateralReaderKey = this.market.reader?.batchKey ?? null;
|
|
294
|
+
const sameReaderDeployment = borrowMarket.reader === this.market.reader ||
|
|
295
|
+
(borrowReaderKey != null && borrowReaderKey === collateralReaderKey);
|
|
296
|
+
if (!sameMarket || !sameChain || !sameReaderDeployment) {
|
|
297
|
+
throw new Error(`${label} token ${borrow.address} belongs to market ${borrowMarket.address} ` +
|
|
298
|
+
`on ${borrowChain ?? "unknown"}, not market ${this.market.address} on ${collateralChain ?? "unknown"} ` +
|
|
299
|
+
`with the same reader deployment.`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
244
302
|
getZapType(zap) {
|
|
245
303
|
return typeof zap === "object" ? zap.type : zap;
|
|
246
304
|
}
|
|
@@ -266,7 +324,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
266
324
|
if (zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
267
325
|
return 18n;
|
|
268
326
|
}
|
|
269
|
-
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined,
|
|
327
|
+
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, this.setup.contracts.OracleManager, this.signer);
|
|
270
328
|
return inputErc20.decimals ?? await inputErc20.contract.decimals();
|
|
271
329
|
}
|
|
272
330
|
}
|
|
@@ -274,8 +332,9 @@ class CToken extends Calldata_1.Calldata {
|
|
|
274
332
|
return FormatConverter_1.default.decimalToBigInt(amount, await this.getZapInputDecimals(zap));
|
|
275
333
|
}
|
|
276
334
|
async assertVaultLeverageBorrowAssetSupported(borrow, type) {
|
|
335
|
+
this.assertBorrowTokenBelongsToMarket(borrow);
|
|
277
336
|
const expectedAsset = type === "native-vault"
|
|
278
|
-
? this.
|
|
337
|
+
? this.currentChainAssets.wrapped_native
|
|
279
338
|
: await this.getVaultAsset(false);
|
|
280
339
|
const actualAsset = borrow.asset.address;
|
|
281
340
|
if (actualAsset.toLowerCase() === expectedAsset.toLowerCase()) {
|
|
@@ -384,11 +443,15 @@ class CToken extends Calldata_1.Calldata {
|
|
|
384
443
|
const shares = this.totalSupply === 0n || this.totalAssets === 0n
|
|
385
444
|
? assets
|
|
386
445
|
: (assets * this.totalSupply) / this.totalAssets;
|
|
387
|
-
return bufferBps > 0n ? shares * (
|
|
388
|
-
}
|
|
389
|
-
getMarketLeverageState() {
|
|
390
|
-
const currentCollateralInUsd =
|
|
391
|
-
|
|
446
|
+
return bufferBps > 0n ? shares * (helpers_1.BPS - bufferBps) / helpers_1.BPS : shares;
|
|
447
|
+
}
|
|
448
|
+
getMarketLeverageState(leverageState) {
|
|
449
|
+
const currentCollateralInUsd = leverageState?.collateralUsd != null
|
|
450
|
+
? (0, helpers_1.toDecimal)(leverageState.collateralUsd, 18n)
|
|
451
|
+
: this.market.userCollateral;
|
|
452
|
+
const currentDebt = leverageState?.debtUsd != null
|
|
453
|
+
? (0, helpers_1.toDecimal)(leverageState.debtUsd, 18n)
|
|
454
|
+
: this.market.userDebt;
|
|
392
455
|
const equity = currentCollateralInUsd.sub(currentDebt);
|
|
393
456
|
if (currentCollateralInUsd.lte(0) || equity.lte(0)) {
|
|
394
457
|
return {
|
|
@@ -525,7 +588,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
525
588
|
// into Curvance shares. Buffer the inner preview so exchange-rate drift
|
|
526
589
|
// between quote time and inclusion cannot trip the outer expectedShares
|
|
527
590
|
// check on otherwise-valid deposits/leverage/zaps.
|
|
528
|
-
const vaultShares = vaultSharesRaw * (
|
|
591
|
+
const vaultShares = vaultSharesRaw * (helpers_1.BPS - exports.LEVERAGE.SHARES_BUFFER_BPS) / helpers_1.BPS;
|
|
529
592
|
return this.convertToShares(vaultShares);
|
|
530
593
|
}
|
|
531
594
|
getAsset(asErc20) {
|
|
@@ -588,7 +651,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
588
651
|
if (zap_contract == null) {
|
|
589
652
|
return null;
|
|
590
653
|
}
|
|
591
|
-
return new Zapper_1.Zapper(zap_contract, signer, type, this.setup);
|
|
654
|
+
return new Zapper_1.Zapper(zap_contract, signer, type, this.setup, this.currentDexAgg);
|
|
592
655
|
}
|
|
593
656
|
async isZapAssetApproved(instructions, amount) {
|
|
594
657
|
if (instructions == 'none') {
|
|
@@ -655,7 +718,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
655
718
|
}
|
|
656
719
|
async getAllowance(check_contract, underlying = true) {
|
|
657
720
|
const signer = this.requireSigner();
|
|
658
|
-
const erc20 = new ERC20_1.ERC20(this.provider, underlying ? this.asset.address : this.address, undefined,
|
|
721
|
+
const erc20 = new ERC20_1.ERC20(this.provider, underlying ? this.asset.address : this.address, undefined, this.setup.contracts.OracleManager, this.signer);
|
|
659
722
|
const allowance = await erc20.allowance(signer.address, check_contract);
|
|
660
723
|
return allowance;
|
|
661
724
|
}
|
|
@@ -665,12 +728,12 @@ class CToken extends Calldata_1.Calldata {
|
|
|
665
728
|
* @returns tx
|
|
666
729
|
*/
|
|
667
730
|
async approveUnderlying(amount = null, target = null) {
|
|
668
|
-
const erc20 = new ERC20_1.ERC20(this.provider, this.asset.address, undefined,
|
|
731
|
+
const erc20 = new ERC20_1.ERC20(this.provider, this.asset.address, undefined, this.setup.contracts.OracleManager, this.signer);
|
|
669
732
|
const tx = await erc20.approve(target ? target : this.address, amount);
|
|
670
733
|
return tx;
|
|
671
734
|
}
|
|
672
735
|
async approve(amount = null, spender) {
|
|
673
|
-
const erc20 = new ERC20_1.ERC20(this.provider, this.address, undefined,
|
|
736
|
+
const erc20 = new ERC20_1.ERC20(this.provider, this.address, undefined, this.setup.contracts.OracleManager, this.signer);
|
|
674
737
|
const tx = await erc20.approve(spender, amount);
|
|
675
738
|
return tx;
|
|
676
739
|
}
|
|
@@ -741,7 +804,8 @@ class CToken extends Calldata_1.Calldata {
|
|
|
741
804
|
}
|
|
742
805
|
async transfer(receiver, amount) {
|
|
743
806
|
const shares = this.convertTokenInputToShares(amount);
|
|
744
|
-
|
|
807
|
+
const calldata = this.getCallData("transfer", [receiver, shares]);
|
|
808
|
+
return this.oracleRoute(calldata);
|
|
745
809
|
}
|
|
746
810
|
async redeemCollateral(amount, receiver = null, owner = null) {
|
|
747
811
|
const signer = this.requireSigner();
|
|
@@ -775,21 +839,47 @@ class CToken extends Calldata_1.Calldata {
|
|
|
775
839
|
if (max_shares <= 0n) {
|
|
776
840
|
throw new Error("No cToken shares available to post as collateral.");
|
|
777
841
|
}
|
|
842
|
+
this.assertCollateralCapacity(max_shares);
|
|
778
843
|
const calldata = this.getCallData("postCollateral", [max_shares]);
|
|
779
844
|
const tx = await this.oracleRoute(calldata);
|
|
780
845
|
// Reload collateral state after execution
|
|
781
846
|
await this.fetchUserCollateral();
|
|
782
847
|
return tx;
|
|
783
848
|
}
|
|
849
|
+
assertCollateralCapacity(shares) {
|
|
850
|
+
const collateralCapError = "There is not enough collateral left in this tokens collateral cap for this deposit.";
|
|
851
|
+
const remainingCollateral = this.getRemainingCollateral(false);
|
|
852
|
+
if (remainingCollateral <= 0n)
|
|
853
|
+
throw new Error(collateralCapError);
|
|
854
|
+
if (shares != undefined && shares > remainingCollateral) {
|
|
855
|
+
throw new Error(collateralCapError);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
getZapperExpectedShares(zapper, calldata) {
|
|
859
|
+
if (zapper == null)
|
|
860
|
+
return undefined;
|
|
861
|
+
let expectedShares;
|
|
862
|
+
try {
|
|
863
|
+
const decoded = zapper.contract.interface.decodeFunctionData("swapAndDeposit", calldata);
|
|
864
|
+
expectedShares = BigInt(decoded[3]);
|
|
865
|
+
}
|
|
866
|
+
catch {
|
|
867
|
+
return undefined;
|
|
868
|
+
}
|
|
869
|
+
if (expectedShares <= 0n) {
|
|
870
|
+
throw new Error("Zap expected shares must be greater than zero.");
|
|
871
|
+
}
|
|
872
|
+
return expectedShares;
|
|
873
|
+
}
|
|
784
874
|
async getZapBalance(zap) {
|
|
785
875
|
const signer = this.requireSigner();
|
|
786
876
|
let asset;
|
|
787
877
|
if (typeof zap === 'object') {
|
|
788
878
|
if (zap.type === 'native-vault' || zap.type === 'native-simple' || zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
789
|
-
asset = new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account);
|
|
879
|
+
asset = new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account, this.currentChainAssets);
|
|
790
880
|
}
|
|
791
881
|
else {
|
|
792
|
-
asset = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined,
|
|
882
|
+
asset = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, this.setup.contracts.OracleManager, this.signer);
|
|
793
883
|
}
|
|
794
884
|
}
|
|
795
885
|
else {
|
|
@@ -801,10 +891,10 @@ class CToken extends Calldata_1.Calldata {
|
|
|
801
891
|
asset = await this.getVaultAsset(true);
|
|
802
892
|
break;
|
|
803
893
|
case 'native-vault':
|
|
804
|
-
asset = new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account);
|
|
894
|
+
asset = new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account, this.currentChainAssets);
|
|
805
895
|
break;
|
|
806
896
|
case 'native-simple':
|
|
807
|
-
asset = new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account);
|
|
897
|
+
asset = new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account, this.currentChainAssets);
|
|
808
898
|
break;
|
|
809
899
|
default: throw new Error("Unsupported zap type for balance fetch");
|
|
810
900
|
}
|
|
@@ -883,7 +973,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
883
973
|
}
|
|
884
974
|
async convertToShares(assets, bufferBps = exports.LEVERAGE.SHARES_BUFFER_BPS) {
|
|
885
975
|
const shares = await this.contract.convertToShares(assets);
|
|
886
|
-
return bufferBps > 0n ? shares * (
|
|
976
|
+
return bufferBps > 0n ? shares * (helpers_1.BPS - bufferBps) / helpers_1.BPS : shares;
|
|
887
977
|
}
|
|
888
978
|
async maxRedemption(in_shares = false, bufferTime = 0n, breakdown = false) {
|
|
889
979
|
const data = await this.market.reader.maxRedemptionOf(this.getAccountOrThrow(), this, bufferTime);
|
|
@@ -912,14 +1002,14 @@ class CToken extends Calldata_1.Calldata {
|
|
|
912
1002
|
let tokens_exclude = [this.asset.address.toLocaleLowerCase()];
|
|
913
1003
|
if (this.zapTypes.includes('native-vault')) {
|
|
914
1004
|
tokens.push({
|
|
915
|
-
interface: new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account),
|
|
1005
|
+
interface: new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account, this.currentChainAssets),
|
|
916
1006
|
type: 'native-vault'
|
|
917
1007
|
});
|
|
918
1008
|
tokens_exclude.push(helpers_1.EMPTY_ADDRESS.toLowerCase(), helpers_1.NATIVE_ADDRESS.toLowerCase());
|
|
919
1009
|
}
|
|
920
1010
|
if (this.zapTypes.includes('native-simple')) {
|
|
921
1011
|
tokens.push({
|
|
922
|
-
interface: new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account),
|
|
1012
|
+
interface: new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account, this.currentChainAssets),
|
|
923
1013
|
type: 'native-simple'
|
|
924
1014
|
});
|
|
925
1015
|
if (!this.zapTypes.includes('native-vault')) {
|
|
@@ -934,20 +1024,20 @@ class CToken extends Calldata_1.Calldata {
|
|
|
934
1024
|
});
|
|
935
1025
|
tokens_exclude.push(vault_asset.address.toLocaleLowerCase());
|
|
936
1026
|
}
|
|
937
|
-
if (this.zapTypes.includes('simple') && this.
|
|
938
|
-
let dexAggSearch = await this.
|
|
1027
|
+
if (this.zapTypes.includes('simple') && this.hasExecutableDexRoute) {
|
|
1028
|
+
let dexAggSearch = await this.currentDexAgg.getAvailableTokens(this.provider, search, this.account);
|
|
939
1029
|
tokens = tokens.concat(dexAggSearch.filter(token => !tokens_exclude.includes(token.interface.address.toLocaleLowerCase())));
|
|
940
1030
|
// Add native MON as a zap option for any token with a simple zapper
|
|
941
1031
|
// (not just wrapped native). The simple zapper handles wrapping + swapping.
|
|
942
1032
|
if (!tokens_exclude.includes(helpers_1.NATIVE_ADDRESS.toLowerCase()) && !this.isWrappedNative) {
|
|
943
1033
|
tokens.push({
|
|
944
|
-
interface: new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account),
|
|
1034
|
+
interface: new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account, this.currentChainAssets),
|
|
945
1035
|
type: 'simple'
|
|
946
1036
|
});
|
|
947
1037
|
tokens_exclude.push(helpers_1.NATIVE_ADDRESS.toLowerCase());
|
|
948
1038
|
}
|
|
949
1039
|
}
|
|
950
|
-
tokens = tokens.filter(token => token.type === 'none' || !
|
|
1040
|
+
tokens = tokens.filter(token => token.type === 'none' || !this.isZapSymbolExcluded(token.interface.symbol));
|
|
951
1041
|
if (search) {
|
|
952
1042
|
const lowerSearch = search.toLowerCase();
|
|
953
1043
|
tokens = tokens.filter(token => (token.interface.name ?? '').toLowerCase().includes(lowerSearch) ||
|
|
@@ -963,30 +1053,29 @@ class CToken extends Calldata_1.Calldata {
|
|
|
963
1053
|
* Single-RPC snapshot of fresh position state for leverage operations.
|
|
964
1054
|
* Calls ProtocolReader.getLeverageSnapshot which internally uses
|
|
965
1055
|
* hypotheticalLiquidityOf for aggregate position + fresh oracle prices
|
|
966
|
-
* + projected debt balance. Updates
|
|
967
|
-
*
|
|
1056
|
+
* + projected debt balance. Updates only token price/share caches; leverage
|
|
1057
|
+
* previews consume the returned aggregate values explicitly so failed or
|
|
1058
|
+
* simulated plans cannot leave market user totals ahead of token user rows.
|
|
968
1059
|
*
|
|
969
1060
|
* Returns the snapshot for direct use where needed (e.g. debtTokenBalance
|
|
970
1061
|
* for full deleverage swap sizing).
|
|
971
1062
|
*/
|
|
972
1063
|
async _getLeverageSnapshot(borrow) {
|
|
1064
|
+
this.assertBorrowTokenBelongsToMarket(borrow);
|
|
973
1065
|
const snapshot = await this.market.reader.getLeverageSnapshot(this.getAccountOrThrow(), this.address, borrow.address, 120n);
|
|
974
1066
|
if (snapshot.oracleError) {
|
|
975
1067
|
throw new Error(`Oracle error fetching leverage snapshot for ${this.symbol}/${borrow.symbol}`);
|
|
976
1068
|
}
|
|
977
|
-
// Update cache so preview functions read fresh values
|
|
978
1069
|
this.cache.assetPrice = snapshot.collateralAssetPrice;
|
|
979
1070
|
this.cache.sharePrice = snapshot.sharePrice;
|
|
980
1071
|
borrow.cache.assetPrice = snapshot.debtAssetPrice;
|
|
981
|
-
this.market.cache.user.collateral = snapshot.collateralUsd;
|
|
982
|
-
this.market.cache.user.debt = snapshot.debtUsd;
|
|
983
1072
|
return snapshot;
|
|
984
1073
|
}
|
|
985
1074
|
/**
|
|
986
1075
|
* Compute slippage BPS for the contract's checkSlippage modifier when
|
|
987
1076
|
* leveraging up. Under Curvance's permanent single-oracle architecture
|
|
988
1077
|
* with fresh state from _getLeverageSnapshot, the only forced equity
|
|
989
|
-
* loss comes from wei-level share rounding plus possible
|
|
1078
|
+
* loss comes from wei-level share rounding plus possible oracle price
|
|
990
1079
|
* drift between snapshot RPC and tx broadcast — both small constants
|
|
991
1080
|
* in absolute terms. We add a small flat buffer; the contract's
|
|
992
1081
|
* equity-fraction denominator amplifies it by (L-1)x automatically.
|
|
@@ -994,7 +1083,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
994
1083
|
* unaffected — that's the layer that bounds MEV extraction.
|
|
995
1084
|
*
|
|
996
1085
|
* Applied uniformly to simple AND vault/native-vault leverage-up paths.
|
|
997
|
-
* Simple path uses the buffer for share-rounding +
|
|
1086
|
+
* Simple path uses the buffer for share-rounding + oracle drift as
|
|
998
1087
|
* described above. Vault paths inherit the flat 10 bps through the
|
|
999
1088
|
* shared `slippage` variable before the per-branch
|
|
1000
1089
|
* `amplifyContractSlippage(..., LEVERAGE_UP_VAULT_DRIFT_BPS)` expansion;
|
|
@@ -1010,11 +1099,13 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1010
1099
|
return slippage + exports.LEVERAGE.LEVERAGE_UP_BUFFER_BPS;
|
|
1011
1100
|
}
|
|
1012
1101
|
assertSimpleLeverageSwapAssetsDiffer(borrow) {
|
|
1102
|
+
this.assertBorrowTokenBelongsToMarket(borrow);
|
|
1013
1103
|
if (borrow.asset.address.toLowerCase() === this.asset.address.toLowerCase()) {
|
|
1014
1104
|
throw new Error("Simple leverage requires distinct collateral and borrow assets.");
|
|
1015
1105
|
}
|
|
1016
1106
|
}
|
|
1017
1107
|
async assertLeverageBorrowCapacity(borrow, borrowAssets) {
|
|
1108
|
+
this.assertBorrowTokenBelongsToMarket(borrow);
|
|
1018
1109
|
if (borrowAssets === 0n) {
|
|
1019
1110
|
return;
|
|
1020
1111
|
}
|
|
@@ -1031,6 +1122,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1031
1122
|
}
|
|
1032
1123
|
}
|
|
1033
1124
|
assertSelectedBorrowDebtCanDeleverage(borrow, selectedDebtAssets, requiredDebtReductionUsd) {
|
|
1125
|
+
this.assertBorrowTokenBelongsToMarket(borrow);
|
|
1034
1126
|
if (requiredDebtReductionUsd.lte(0)) {
|
|
1035
1127
|
return;
|
|
1036
1128
|
}
|
|
@@ -1052,15 +1144,16 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1052
1144
|
return (0, decimal_js_1.default)(1);
|
|
1053
1145
|
return collateralAfterDeposit.div(equityAfterDeposit);
|
|
1054
1146
|
}
|
|
1055
|
-
resolveLeverageUpPreview({ operation, targetLeverage, borrow, depositAssets = 0n, positionManagerType, }) {
|
|
1056
|
-
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1147
|
+
resolveLeverageUpPreview({ operation, targetLeverage, borrow, depositAssets = 0n, positionManagerType, leverageState, }) {
|
|
1148
|
+
this.assertBorrowTokenBelongsToMarket(borrow);
|
|
1149
|
+
const currentState = this.getMarketLeverageState(leverageState);
|
|
1150
|
+
const currentLeverage = currentState.currentLeverage ?? (0, decimal_js_1.default)(1);
|
|
1151
|
+
const currentCollateralInUsd = currentState.currentCollateralInUsd;
|
|
1059
1152
|
const depositInAssets = FormatConverter_1.default.bigIntToDecimal(depositAssets, this.asset.decimals);
|
|
1060
1153
|
const depositInUsd = depositAssets > 0n
|
|
1061
1154
|
? this.convertTokensToUsd(depositAssets, true)
|
|
1062
1155
|
: (0, decimal_js_1.default)(0);
|
|
1063
|
-
const currentDebt =
|
|
1156
|
+
const currentDebt = currentState.currentDebt;
|
|
1064
1157
|
const effectiveCurrentLeverage = depositAssets > 0n
|
|
1065
1158
|
? this.computePostDepositNaturalLeverage(currentCollateralInUsd, currentDebt, depositInUsd)
|
|
1066
1159
|
: currentLeverage;
|
|
@@ -1101,7 +1194,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1101
1194
|
targetLeverage: resolvedTargetLeverage,
|
|
1102
1195
|
})
|
|
1103
1196
|
: 0n;
|
|
1104
|
-
const feeAssets = borrowAmount.mul((0, decimal_js_1.default)(Number(feeBps))).div(
|
|
1197
|
+
const feeAssets = borrowAmount.mul((0, decimal_js_1.default)(Number(feeBps))).div(helpers_1.BPS_DECIMAL);
|
|
1105
1198
|
const feeUsd = feeAssets.mul(borrowPrice);
|
|
1106
1199
|
const collateralIncreaseFromBorrow = decimal_js_1.default.max(debtIncrease.sub(feeUsd), (0, decimal_js_1.default)(0));
|
|
1107
1200
|
const collateralIncrease = depositInUsd.add(collateralIncreaseFromBorrow);
|
|
@@ -1126,36 +1219,39 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1126
1219
|
feeUsd,
|
|
1127
1220
|
};
|
|
1128
1221
|
}
|
|
1129
|
-
previewDepositAndLeverage(newLeverage, borrow, depositAmount, positionManagerType) {
|
|
1222
|
+
previewDepositAndLeverage(newLeverage, borrow, depositAmount, positionManagerType, leverageState) {
|
|
1130
1223
|
return this.resolveLeverageUpPreview({
|
|
1131
1224
|
operation: 'deposit-and-leverage',
|
|
1132
1225
|
targetLeverage: newLeverage,
|
|
1133
1226
|
borrow,
|
|
1134
1227
|
depositAssets: depositAmount,
|
|
1135
1228
|
positionManagerType,
|
|
1229
|
+
leverageState,
|
|
1136
1230
|
});
|
|
1137
1231
|
}
|
|
1138
|
-
previewLeverageUp(newLeverage, borrow, depositAmount, positionManagerType) {
|
|
1232
|
+
previewLeverageUp(newLeverage, borrow, depositAmount, positionManagerType, leverageState) {
|
|
1139
1233
|
if ((depositAmount ?? 0n) > 0n) {
|
|
1140
|
-
return this.previewDepositAndLeverage(newLeverage, borrow, depositAmount, positionManagerType);
|
|
1234
|
+
return this.previewDepositAndLeverage(newLeverage, borrow, depositAmount, positionManagerType, leverageState);
|
|
1141
1235
|
}
|
|
1142
1236
|
return this.resolveLeverageUpPreview({
|
|
1143
1237
|
operation: 'leverage-up',
|
|
1144
1238
|
targetLeverage: newLeverage,
|
|
1145
1239
|
borrow,
|
|
1146
1240
|
positionManagerType,
|
|
1241
|
+
leverageState,
|
|
1147
1242
|
});
|
|
1148
1243
|
}
|
|
1149
|
-
previewLeverageDown(newLeverage, currentLeverage, borrow) {
|
|
1244
|
+
previewLeverageDown(newLeverage, currentLeverage, borrow, leverageState) {
|
|
1245
|
+
this.assertBorrowTokenBelongsToMarket(borrow);
|
|
1150
1246
|
if (newLeverage.gte(currentLeverage)) {
|
|
1151
1247
|
throw new Error("New leverage must be less than current leverage");
|
|
1152
1248
|
}
|
|
1153
1249
|
if (newLeverage.lt((0, decimal_js_1.default)(1))) {
|
|
1154
1250
|
throw new Error("New leverage must be at least 1");
|
|
1155
1251
|
}
|
|
1156
|
-
const
|
|
1157
|
-
const collateralInUsd =
|
|
1158
|
-
const currentDebt =
|
|
1252
|
+
const currentState = this.getMarketLeverageState(leverageState);
|
|
1253
|
+
const collateralInUsd = currentState.currentCollateralInUsd;
|
|
1254
|
+
const currentDebt = currentState.currentDebt;
|
|
1159
1255
|
const equity = collateralInUsd.sub(currentDebt);
|
|
1160
1256
|
if (equity.lte(0)) {
|
|
1161
1257
|
throw new Error("Position has no positive equity to deleverage.");
|
|
@@ -1180,7 +1276,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1180
1276
|
currentLeverage,
|
|
1181
1277
|
targetLeverage: newLeverage,
|
|
1182
1278
|
}) : 0n;
|
|
1183
|
-
const feeUsd = collateralAssetReductionUsd.mul((0, decimal_js_1.default)(Number(feeBps))).div(
|
|
1279
|
+
const feeUsd = collateralAssetReductionUsd.mul((0, decimal_js_1.default)(Number(feeBps))).div(helpers_1.BPS_DECIMAL);
|
|
1184
1280
|
const feeAssets = this.getPrice(true).gt(0)
|
|
1185
1281
|
? feeUsd.div(this.getPrice(true))
|
|
1186
1282
|
: (0, decimal_js_1.default)(0);
|
|
@@ -1199,6 +1295,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1199
1295
|
}
|
|
1200
1296
|
async leverageUp(borrow, newLeverage, type, slippage_ = (0, decimal_js_1.default)(0.05), simulate = false) {
|
|
1201
1297
|
try {
|
|
1298
|
+
this.assertBorrowTokenBelongsToMarket(borrow);
|
|
1202
1299
|
this.requireSigner();
|
|
1203
1300
|
const manager = this.getPositionManager(type);
|
|
1204
1301
|
if (type === 'vault' || type === 'native-vault') {
|
|
@@ -1208,8 +1305,8 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1208
1305
|
this.assertSimpleLeverageSwapAssetsDiffer(borrow);
|
|
1209
1306
|
}
|
|
1210
1307
|
let calldata;
|
|
1211
|
-
await this._getLeverageSnapshot(borrow);
|
|
1212
|
-
const preview = this.previewLeverageUp(newLeverage, borrow, undefined, type);
|
|
1308
|
+
const snapshot = await this._getLeverageSnapshot(borrow);
|
|
1309
|
+
const preview = this.previewLeverageUp(newLeverage, borrow, undefined, type, snapshot);
|
|
1213
1310
|
const slippage = this._leverageUpSlippage(FormatConverter_1.default.percentageToBps(slippage_), preview.targetLeverage);
|
|
1214
1311
|
const { borrowAmount, borrowAssets, feeBps, targetLeverage } = preview;
|
|
1215
1312
|
if (borrowAssets === 0n) {
|
|
@@ -1225,7 +1322,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1225
1322
|
switch (type) {
|
|
1226
1323
|
case 'simple': {
|
|
1227
1324
|
const feeReceiver = feeBps > 0n ? this.setup.feePolicy.feeReceiver : undefined;
|
|
1228
|
-
const { action, quote } = await this.
|
|
1325
|
+
const { action, quote } = await this.currentDexAgg.quoteAction(manager.address, borrow.asset.address, this.asset.address, borrowAssets, slippage, feeBps, feeReceiver);
|
|
1229
1326
|
// Fee-aware slippage expansion now lives inside KyberSwap.quoteAction
|
|
1230
1327
|
// so any caller inherits correct behavior. See KyberSwap.ts for the
|
|
1231
1328
|
// rationale. The fee still reduces swap output, which checkSlippage
|
|
@@ -1280,13 +1377,13 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1280
1377
|
}
|
|
1281
1378
|
async leverageDown(borrowToken, currentLeverage, newLeverage, type, slippage_ = (0, decimal_js_1.default)(0.05), simulate = false) {
|
|
1282
1379
|
try {
|
|
1380
|
+
this.assertBorrowTokenBelongsToMarket(borrowToken);
|
|
1283
1381
|
if (newLeverage.gte(currentLeverage)) {
|
|
1284
1382
|
if (simulate)
|
|
1285
1383
|
return { success: false, error: "New leverage must be less than current leverage" };
|
|
1286
1384
|
throw new Error("New leverage must be less than current leverage");
|
|
1287
1385
|
}
|
|
1288
1386
|
this.requireSigner();
|
|
1289
|
-
const config = this.currentChainConfig;
|
|
1290
1387
|
const slippage = (0, helpers_1.toBps)(slippage_);
|
|
1291
1388
|
const manager = this.getPositionManager(type);
|
|
1292
1389
|
if (type === 'simple') {
|
|
@@ -1294,7 +1391,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1294
1391
|
}
|
|
1295
1392
|
let calldata;
|
|
1296
1393
|
const snapshot = await this._getLeverageSnapshot(borrowToken);
|
|
1297
|
-
const preview = this.previewLeverageDown(newLeverage, currentLeverage);
|
|
1394
|
+
const preview = this.previewLeverageDown(newLeverage, currentLeverage, undefined, snapshot);
|
|
1298
1395
|
const { collateralAssetReduction } = preview;
|
|
1299
1396
|
const isFullDeleverage = newLeverage.equals(1);
|
|
1300
1397
|
const maxTokenCollateral = this.virtualConvertToAssets(this.readFreshUserCache("userCollateral", "executing leverage down"));
|
|
@@ -1339,7 +1436,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1339
1436
|
// Additive approximation is accurate to sub-bp at typical
|
|
1340
1437
|
// fee+overhead magnitudes (< 100 bps combined).
|
|
1341
1438
|
const overheadBps = exports.LEVERAGE.DELEVERAGE_OVERHEAD_BPS + feeBps;
|
|
1342
|
-
swapCollateral = ceilDiv(debtInCollateral * (
|
|
1439
|
+
swapCollateral = ceilDiv(debtInCollateral * (helpers_1.BPS + overheadBps), helpers_1.BPS);
|
|
1343
1440
|
if (swapCollateral > maxTokenCollateral) {
|
|
1344
1441
|
const error = "Selected collateral token does not have enough posted collateral to fully deleverage.";
|
|
1345
1442
|
if (simulate) {
|
|
@@ -1356,7 +1453,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1356
1453
|
// from input before swapping, so without compensation
|
|
1357
1454
|
// the swap underdelivers and actual leverage is slightly
|
|
1358
1455
|
// higher than target.
|
|
1359
|
-
swapCollateral = ceilDiv(swapCollateral *
|
|
1456
|
+
swapCollateral = ceilDiv(swapCollateral * helpers_1.BPS, helpers_1.BPS - feeBps);
|
|
1360
1457
|
}
|
|
1361
1458
|
}
|
|
1362
1459
|
if (!isFullDeleverage && swapCollateral > maxTokenCollateral) {
|
|
@@ -1366,7 +1463,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1366
1463
|
}
|
|
1367
1464
|
throw new Error(error);
|
|
1368
1465
|
}
|
|
1369
|
-
const { action, quote } = await
|
|
1466
|
+
const { action, quote } = await this.currentDexAgg.quoteAction(manager.address, this.asset.address, borrowToken.asset.address, swapCollateral, slippage, feeBps, feeReceiver);
|
|
1370
1467
|
// Fee-aware slippage expansion for `_swapSafe` is handled by
|
|
1371
1468
|
// KyberSwap.quoteAction. See KyberSwap.ts for rationale.
|
|
1372
1469
|
// In the current PositionManager, `repayAssets` is only a
|
|
@@ -1423,6 +1520,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1423
1520
|
}
|
|
1424
1521
|
async depositAndLeverage(depositAmount, borrow, multiplier, type, slippage_ = (0, decimal_js_1.default)(0.05), simulate = false) {
|
|
1425
1522
|
try {
|
|
1523
|
+
this.assertBorrowTokenBelongsToMarket(borrow);
|
|
1426
1524
|
if (multiplier.lte((0, decimal_js_1.default)(1))) {
|
|
1427
1525
|
if (simulate)
|
|
1428
1526
|
return { success: false, error: "Multiplier must be greater than 1" };
|
|
@@ -1438,9 +1536,14 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1438
1536
|
}
|
|
1439
1537
|
let calldata;
|
|
1440
1538
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(depositAmount, this.asset.decimals);
|
|
1539
|
+
if (depositAssets <= 0n) {
|
|
1540
|
+
if (simulate)
|
|
1541
|
+
return { success: false, error: "Deposit amount must be greater than zero." };
|
|
1542
|
+
throw new Error("Deposit amount must be greater than zero.");
|
|
1543
|
+
}
|
|
1441
1544
|
await this._checkTokenApproval(this.getPositionManagerDepositApprovalTarget(manager), depositAssets);
|
|
1442
|
-
await this._getLeverageSnapshot(borrow);
|
|
1443
|
-
const preview = this.previewDepositAndLeverage(multiplier, borrow, depositAssets, type);
|
|
1545
|
+
const snapshot = await this._getLeverageSnapshot(borrow);
|
|
1546
|
+
const preview = this.previewDepositAndLeverage(multiplier, borrow, depositAssets, type, snapshot);
|
|
1444
1547
|
if (preview.borrowAssets === 0n) {
|
|
1445
1548
|
if (simulate) {
|
|
1446
1549
|
return {
|
|
@@ -1456,7 +1559,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1456
1559
|
switch (type) {
|
|
1457
1560
|
case 'simple': {
|
|
1458
1561
|
const feeReceiver = feeBps > 0n ? this.setup.feePolicy.feeReceiver : undefined;
|
|
1459
|
-
const { action, quote } = await this.
|
|
1562
|
+
const { action, quote } = await this.currentDexAgg.quoteAction(manager.address, borrow.asset.address, this.asset.address, borrowAssets, slippage, feeBps, feeReceiver);
|
|
1460
1563
|
// Fee-aware slippage expansion for `_swapSafe` is handled by
|
|
1461
1564
|
// KyberSwap.quoteAction. See KyberSwap.ts for rationale.
|
|
1462
1565
|
// Fee amplification: same pattern as leverageUp. See
|
|
@@ -1512,6 +1615,9 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1512
1615
|
receiver ??= signer.address;
|
|
1513
1616
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1514
1617
|
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1618
|
+
if (zapAssets <= 0n) {
|
|
1619
|
+
throw new Error("Deposit amount must be greater than zero.");
|
|
1620
|
+
}
|
|
1515
1621
|
const default_calldata = this.getCallData("deposit", [depositAssets, receiver]);
|
|
1516
1622
|
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, false, default_calldata, receiver);
|
|
1517
1623
|
return this.simulateOracleRoute(calldata, calldata_overrides);
|
|
@@ -1527,11 +1633,20 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1527
1633
|
receiver ??= signer.address;
|
|
1528
1634
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1529
1635
|
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1636
|
+
if (zapAssets <= 0n) {
|
|
1637
|
+
throw new Error("Deposit amount must be greater than zero.");
|
|
1638
|
+
}
|
|
1639
|
+
const depositShares = this.isZapInstruction(zap) ? undefined : this.virtualConvertToShares(depositAssets);
|
|
1640
|
+
this.assertCollateralCapacity(depositShares);
|
|
1530
1641
|
const collateralMethod = receiver.toLowerCase() === signer.address.toLowerCase()
|
|
1531
1642
|
? "depositAsCollateral"
|
|
1532
1643
|
: "depositAsCollateralFor";
|
|
1533
1644
|
const default_calldata = this.getCallData(collateralMethod, [depositAssets, receiver]);
|
|
1534
|
-
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, true, default_calldata, receiver);
|
|
1645
|
+
const { calldata, calldata_overrides, expectedShares } = await this.zap(zapAssets, zap, true, default_calldata, receiver);
|
|
1646
|
+
if (expectedShares !== undefined && expectedShares <= 0n) {
|
|
1647
|
+
throw new Error("Zap expected shares must be greater than zero.");
|
|
1648
|
+
}
|
|
1649
|
+
this.assertCollateralCapacity(expectedShares ?? depositShares);
|
|
1535
1650
|
return this.simulateOracleRoute(calldata, calldata_overrides);
|
|
1536
1651
|
}
|
|
1537
1652
|
catch (error) {
|
|
@@ -1582,7 +1697,12 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1582
1697
|
default:
|
|
1583
1698
|
throw new Error("This zap type is not supported: " + type_of_zap);
|
|
1584
1699
|
}
|
|
1585
|
-
return {
|
|
1700
|
+
return {
|
|
1701
|
+
calldata,
|
|
1702
|
+
calldata_overrides,
|
|
1703
|
+
zapper,
|
|
1704
|
+
expectedShares: this.getZapperExpectedShares(zapper, calldata),
|
|
1705
|
+
};
|
|
1586
1706
|
}
|
|
1587
1707
|
async deposit(amount, zap = 'none', receiver = null) {
|
|
1588
1708
|
amount = await this.ensureUnderlyingAmount(amount, zap);
|
|
@@ -1590,6 +1710,9 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1590
1710
|
receiver ??= signer.address;
|
|
1591
1711
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1592
1712
|
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1713
|
+
if (zapAssets <= 0n) {
|
|
1714
|
+
throw new Error("Deposit amount must be greater than zero.");
|
|
1715
|
+
}
|
|
1593
1716
|
await this._checkDepositApprovals(zap, depositAssets, zapAssets);
|
|
1594
1717
|
const default_calldata = this.getCallData("deposit", [depositAssets, receiver]);
|
|
1595
1718
|
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, false, default_calldata, receiver);
|
|
@@ -1601,43 +1724,49 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1601
1724
|
receiver ??= signer.address;
|
|
1602
1725
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1603
1726
|
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1604
|
-
if (
|
|
1605
|
-
|
|
1606
|
-
const remainingCollateral = this.getRemainingCollateral(false);
|
|
1607
|
-
if (remainingCollateral <= 0n)
|
|
1608
|
-
throw new Error(collateralCapError);
|
|
1609
|
-
if (remainingCollateral > 0n) {
|
|
1610
|
-
const shares = this.virtualConvertToShares(depositAssets);
|
|
1611
|
-
if (shares > remainingCollateral) {
|
|
1612
|
-
throw new Error(collateralCapError);
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1727
|
+
if (zapAssets <= 0n) {
|
|
1728
|
+
throw new Error("Deposit amount must be greater than zero.");
|
|
1615
1729
|
}
|
|
1730
|
+
const depositShares = this.isZapInstruction(zap) ? undefined : this.virtualConvertToShares(depositAssets);
|
|
1731
|
+
this.assertCollateralCapacity(depositShares);
|
|
1616
1732
|
await this._checkDepositApprovals(zap, depositAssets, zapAssets, true, receiver);
|
|
1617
1733
|
const collateralMethod = receiver.toLowerCase() === signer.address.toLowerCase()
|
|
1618
1734
|
? "depositAsCollateral"
|
|
1619
1735
|
: "depositAsCollateralFor";
|
|
1620
1736
|
const default_calldata = this.getCallData(collateralMethod, [depositAssets, receiver]);
|
|
1621
|
-
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, true, default_calldata, receiver);
|
|
1737
|
+
const { calldata, calldata_overrides, expectedShares } = await this.zap(zapAssets, zap, true, default_calldata, receiver);
|
|
1738
|
+
if (expectedShares !== undefined && expectedShares <= 0n) {
|
|
1739
|
+
throw new Error("Zap expected shares must be greater than zero.");
|
|
1740
|
+
}
|
|
1741
|
+
this.assertCollateralCapacity(expectedShares ?? depositShares);
|
|
1622
1742
|
return this.oracleRoute(calldata, calldata_overrides, receiver);
|
|
1623
1743
|
}
|
|
1624
1744
|
async redeem(amount) {
|
|
1625
1745
|
const signer = this.requireSigner();
|
|
1626
1746
|
const receiver = signer.address;
|
|
1627
1747
|
const owner = signer.address;
|
|
1748
|
+
const converted_shares = this.convertTokenInputToShares(amount);
|
|
1749
|
+
if (converted_shares <= 0n) {
|
|
1750
|
+
throw new Error("Redeem amount must be greater than zero.");
|
|
1751
|
+
}
|
|
1628
1752
|
const buffer = this.getExecutionDebtBufferTime();
|
|
1629
1753
|
const balance_avail = await this.balanceOf(signer.address);
|
|
1630
1754
|
const max_shares = await this.maxRedemption(true, buffer);
|
|
1631
|
-
const converted_shares = this.convertTokenInputToShares(amount);
|
|
1632
1755
|
const maxExecutableShares = max_shares < balance_avail ? max_shares : balance_avail;
|
|
1633
1756
|
let shares = maxExecutableShares < converted_shares ? maxExecutableShares : converted_shares;
|
|
1634
1757
|
if (maxExecutableShares === balance_avail && balance_avail - shares <= 10n) {
|
|
1635
1758
|
shares = balance_avail;
|
|
1636
1759
|
}
|
|
1760
|
+
if (shares <= 0n) {
|
|
1761
|
+
throw new Error("No redeemable cToken shares available.");
|
|
1762
|
+
}
|
|
1637
1763
|
const calldata = this.getCallData("redeem", [shares, receiver, owner]);
|
|
1638
1764
|
return this.oracleRoute(calldata);
|
|
1639
1765
|
}
|
|
1640
1766
|
async redeemShares(amount) {
|
|
1767
|
+
if (amount <= 0n) {
|
|
1768
|
+
throw new Error("Redeem amount must be greater than zero.");
|
|
1769
|
+
}
|
|
1641
1770
|
const signer = this.requireSigner();
|
|
1642
1771
|
const receiver = signer.address;
|
|
1643
1772
|
const owner = signer.address;
|
|
@@ -1701,36 +1830,6 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1701
1830
|
async convertSharesToUsd(tokenAmount) {
|
|
1702
1831
|
return this.convertSharesToUsdSync(tokenAmount);
|
|
1703
1832
|
}
|
|
1704
|
-
buildMultiCallAction(calldata, target = this.address) {
|
|
1705
|
-
return {
|
|
1706
|
-
target,
|
|
1707
|
-
isPriceUpdate: false,
|
|
1708
|
-
data: calldata
|
|
1709
|
-
};
|
|
1710
|
-
}
|
|
1711
|
-
hasNonZeroNativeValueOverride(override) {
|
|
1712
|
-
const value = override.value;
|
|
1713
|
-
if (value == null) {
|
|
1714
|
-
return false;
|
|
1715
|
-
}
|
|
1716
|
-
if (typeof value === "bigint") {
|
|
1717
|
-
return value > 0n;
|
|
1718
|
-
}
|
|
1719
|
-
if (typeof value === "number") {
|
|
1720
|
-
return value > 0;
|
|
1721
|
-
}
|
|
1722
|
-
if (typeof value === "string") {
|
|
1723
|
-
return BigInt(value) > 0n;
|
|
1724
|
-
}
|
|
1725
|
-
return true;
|
|
1726
|
-
}
|
|
1727
|
-
assertOracleMulticallSupportsValue(priceUpdates, override) {
|
|
1728
|
-
if (priceUpdates.length === 0 || !this.hasNonZeroNativeValueOverride(override)) {
|
|
1729
|
-
return;
|
|
1730
|
-
}
|
|
1731
|
-
throw new Error("Native gas-token zaps cannot be combined with oracle price-update multicalls. " +
|
|
1732
|
-
"Use the wrapped-native zap path or retry when no oracle update is required.");
|
|
1733
|
-
}
|
|
1734
1833
|
async _checkPositionManagerApproval(manager) {
|
|
1735
1834
|
const isApproved = await this.isPluginApproved(manager.type, 'positionManager');
|
|
1736
1835
|
if (!isApproved) {
|
|
@@ -1791,7 +1890,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1791
1890
|
return null;
|
|
1792
1891
|
}
|
|
1793
1892
|
return {
|
|
1794
|
-
token: new ERC20_1.ERC20(this.provider, instructions.inputToken, undefined,
|
|
1893
|
+
token: new ERC20_1.ERC20(this.provider, instructions.inputToken, undefined, this.setup.contracts.OracleManager, this.signer),
|
|
1795
1894
|
spender,
|
|
1796
1895
|
spenderLabel: `${zapType} Zapper`,
|
|
1797
1896
|
};
|
|
@@ -1851,60 +1950,20 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1851
1950
|
}
|
|
1852
1951
|
async oracleRoute(calldata, override = {}, reloadAccount = null) {
|
|
1853
1952
|
const signer = this.requireSigner();
|
|
1854
|
-
const price_updates = await this.getPriceUpdates();
|
|
1855
|
-
this.assertOracleMulticallSupportsValue(price_updates, override);
|
|
1856
|
-
if (price_updates.length > 0) {
|
|
1857
|
-
const actionTarget = (override.to ?? this.address);
|
|
1858
|
-
const token_action = this.buildMultiCallAction(calldata, actionTarget);
|
|
1859
|
-
calldata = this.getCallData("multicall", [[...price_updates, token_action]]);
|
|
1860
|
-
}
|
|
1861
1953
|
const tx = await this.executeCallData(calldata, override);
|
|
1862
1954
|
if (typeof tx.wait === "function") {
|
|
1863
1955
|
await tx.wait();
|
|
1864
1956
|
}
|
|
1865
|
-
const
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1957
|
+
const signerAddress = signer.address;
|
|
1958
|
+
const refreshAccount = reloadAccount?.toLowerCase() === signerAddress.toLowerCase()
|
|
1959
|
+
? reloadAccount
|
|
1960
|
+
: signerAddress;
|
|
1961
|
+
await this.market.reloadUserData(refreshAccount);
|
|
1869
1962
|
return tx;
|
|
1870
1963
|
}
|
|
1871
1964
|
async simulateOracleRoute(calldata, override = {}) {
|
|
1872
|
-
const price_updates = await this.getPriceUpdates();
|
|
1873
|
-
this.assertOracleMulticallSupportsValue(price_updates, override);
|
|
1874
|
-
if (price_updates.length > 0) {
|
|
1875
|
-
const actionTarget = (override.to ?? this.address);
|
|
1876
|
-
const token_action = this.buildMultiCallAction(calldata, actionTarget);
|
|
1877
|
-
calldata = this.getCallData("multicall", [[...price_updates, token_action]]);
|
|
1878
|
-
}
|
|
1879
1965
|
return this.simulateCallData(calldata, override);
|
|
1880
1966
|
}
|
|
1881
|
-
getRedstonePriceUpdateTokens() {
|
|
1882
|
-
const candidates = (this.market?.tokens?.length ? this.market.tokens : [this]);
|
|
1883
|
-
const seenAssets = new Set();
|
|
1884
|
-
const tokens = [];
|
|
1885
|
-
for (const token of candidates) {
|
|
1886
|
-
if (!token.adapters?.includes(ProtocolReader_1.AdaptorTypes.REDSTONE_CORE)) {
|
|
1887
|
-
continue;
|
|
1888
|
-
}
|
|
1889
|
-
let assetAddress;
|
|
1890
|
-
try {
|
|
1891
|
-
assetAddress = token.getAsset(false);
|
|
1892
|
-
}
|
|
1893
|
-
catch {
|
|
1894
|
-
assetAddress = token.cache?.asset?.address ?? token.address;
|
|
1895
|
-
}
|
|
1896
|
-
const key = assetAddress.toLowerCase();
|
|
1897
|
-
if (seenAssets.has(key)) {
|
|
1898
|
-
continue;
|
|
1899
|
-
}
|
|
1900
|
-
seenAssets.add(key);
|
|
1901
|
-
tokens.push(token);
|
|
1902
|
-
}
|
|
1903
|
-
return tokens;
|
|
1904
|
-
}
|
|
1905
|
-
async getPriceUpdates() {
|
|
1906
|
-
return Promise.all(this.getRedstonePriceUpdateTokens().map((token) => Redstone_1.Redstone.buildMultiCallAction(token)));
|
|
1907
|
-
}
|
|
1908
1967
|
}
|
|
1909
1968
|
exports.CToken = CToken;
|
|
1910
1969
|
//# sourceMappingURL=CToken.js.map
|