@strkfarm/sdk 2.0.0-dev.3 → 2.0.0-dev.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +190 -36
- package/dist/cli.mjs +188 -34
- package/dist/index.browser.global.js +78475 -45620
- package/dist/index.browser.mjs +19580 -9901
- package/dist/index.d.ts +3763 -1424
- package/dist/index.js +20977 -11063
- package/dist/index.mjs +20945 -11087
- package/package.json +1 -1
- package/src/data/avnu.abi.json +840 -0
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/dataTypes/_bignumber.ts +13 -4
- package/src/dataTypes/bignumber.browser.ts +6 -1
- package/src/dataTypes/bignumber.node.ts +5 -1
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +76 -41
- package/src/index.browser.ts +2 -1
- package/src/interfaces/common.tsx +175 -3
- package/src/modules/ExtendedWrapperSDk/types.ts +28 -5
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +275 -59
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +4 -4
- package/src/modules/ekubo-pricer.ts +79 -0
- package/src/modules/ekubo-quoter.ts +48 -30
- package/src/modules/erc20.ts +17 -0
- package/src/modules/harvests.ts +43 -29
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-from-api.ts +156 -15
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +40 -4
- package/src/modules/pricerBase.ts +2 -1
- package/src/node/deployer.ts +36 -1
- package/src/node/pricer-redis.ts +2 -1
- package/src/strategies/base-strategy.ts +78 -10
- package/src/strategies/ekubo-cl-vault.tsx +906 -347
- package/src/strategies/factory.ts +159 -0
- package/src/strategies/index.ts +7 -1
- package/src/strategies/registry.ts +239 -0
- package/src/strategies/sensei.ts +335 -7
- package/src/strategies/svk-strategy.ts +97 -27
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +2 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +180 -265
- package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
- package/src/strategies/universal-adapters/common-adapter.ts +206 -203
- package/src/strategies/universal-adapters/extended-adapter.ts +490 -316
- package/src/strategies/universal-adapters/index.ts +11 -8
- package/src/strategies/universal-adapters/svk-troves-adapter.ts +364 -0
- package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
- package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +200 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
- package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +476 -0
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1067 -704
- package/src/strategies/universal-adapters/vesu-position-common.ts +251 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
- package/src/strategies/universal-lst-muliplier-strategy.tsx +397 -204
- package/src/strategies/universal-strategy.tsx +1426 -1173
- package/src/strategies/vesu-extended-strategy/services/executionService.ts +2233 -0
- package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +4087 -0
- package/src/strategies/vesu-extended-strategy/services/ltv-imbalance-rebalance-math.ts +783 -0
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +38 -16
- package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +88 -0
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +1 -0
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +5 -6
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +259 -103
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +688 -817
- package/src/strategies/vesu-rebalance.tsx +255 -152
- package/src/utils/cacheClass.ts +11 -2
- package/src/utils/health-factor-math.ts +4 -1
- package/src/utils/index.ts +3 -1
- package/src/utils/logger.browser.ts +22 -4
- package/src/utils/logger.node.ts +259 -24
- package/src/utils/starknet-call-parser.ts +1036 -0
- package/src/utils/strategy-utils.ts +61 -0
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
|
@@ -1,333 +1,447 @@
|
|
|
1
1
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { Protocols, TokenInfo } from "@/interfaces";
|
|
3
|
+
import {
|
|
4
|
+
BaseAdapter,
|
|
5
|
+
BaseAdapterConfig,
|
|
6
|
+
SupportedPosition,
|
|
7
|
+
PositionInfo,
|
|
8
|
+
PositionAPY,
|
|
9
|
+
APYType,
|
|
10
|
+
ManageCall,
|
|
11
|
+
AdapterLeafType,
|
|
12
|
+
GenerateCallFn,
|
|
13
|
+
PositionAmount,
|
|
14
|
+
SwapPriceInfo,
|
|
15
|
+
} from "./baseAdapter";
|
|
16
|
+
import {
|
|
17
|
+
SIMPLE_SANITIZER,
|
|
18
|
+
SIMPLE_SANITIZER_V2,
|
|
19
|
+
toBigInt,
|
|
20
|
+
} from "./adapter-utils";
|
|
6
21
|
import { hash, uint256, Contract, CairoCustomEnum, num } from "starknet";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
import {
|
|
23
|
+
VesuAdapter,
|
|
24
|
+
getVesuSingletonAddress,
|
|
25
|
+
Swap,
|
|
26
|
+
IncreaseLeverParams,
|
|
27
|
+
DecreaseLeverParams,
|
|
28
|
+
} from "./vesu-adapter";
|
|
29
|
+
import { assert, logger } from "@/utils";
|
|
30
|
+
import VesuMultiplyAbi from "@/data/vesu-multiple.abi.json";
|
|
31
|
+
import { EkuboQuote, EkuboQuoter, TokenMarketData } from "@/modules";
|
|
14
32
|
import { calculateDebtReductionAmountForWithdrawal } from "../vesu-extended-strategy/utils/helper";
|
|
15
33
|
import { HealthFactorMath } from "@/utils/health-factor-math";
|
|
16
34
|
import { MAX_LIQUIDATION_RATIO } from "../vesu-extended-strategy/utils/constants";
|
|
35
|
+
import {
|
|
36
|
+
VesuPositionCommonContext,
|
|
37
|
+
getVesuCommonAPY,
|
|
38
|
+
getVesuCommonMaxBorrowableAPY,
|
|
39
|
+
getVesuCommonMaxDeposit,
|
|
40
|
+
getVesuCommonMaxWithdraw,
|
|
41
|
+
getVesuCommonPosition,
|
|
42
|
+
} from "./vesu-position-common";
|
|
43
|
+
|
|
44
|
+
const MIN_REMAINING_DEBT_USD = 11;
|
|
45
|
+
const SCALE_128 = BigInt('1000000000000000000'); // 1e18 (matches vesu::units::SCALE_128)
|
|
46
|
+
const MIN_SQRT_RATIO_LIMIT = BigInt('18446748437148339061');
|
|
47
|
+
const MAX_SQRT_RATIO_LIMIT = BigInt('6277100250585753475930931601400621808602321654880405518632');
|
|
48
|
+
|
|
49
|
+
export interface VesuDepositParams {
|
|
50
|
+
amount: Web3Number;
|
|
51
|
+
marginSwap?: {
|
|
52
|
+
marginToken: TokenInfo;
|
|
53
|
+
};
|
|
54
|
+
leverSwap?: {
|
|
55
|
+
exactOutput?: Web3Number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface VesuWithdrawParams {
|
|
60
|
+
amount: Web3Number;
|
|
61
|
+
withdrawSwap?: {
|
|
62
|
+
outputToken: TokenInfo;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
17
65
|
|
|
18
66
|
export interface VesuMultiplyAdapterConfig extends BaseAdapterConfig {
|
|
19
67
|
poolId: ContractAddr;
|
|
20
68
|
collateral: TokenInfo;
|
|
21
69
|
debt: TokenInfo;
|
|
70
|
+
marginToken: TokenInfo; // in case of using ekubo to do margin swap, this can be diff token, else collateral itself.
|
|
22
71
|
targetHealthFactor: number;
|
|
23
72
|
minHealthFactor: number;
|
|
24
73
|
quoteAmountToFetchPrice: Web3Number;
|
|
74
|
+
minimumVesuMovementAmount: number;
|
|
75
|
+
maxSlippage?: number; // e.g. 0.005 for 0.5%
|
|
25
76
|
}
|
|
26
77
|
|
|
27
|
-
export class VesuMultiplyAdapter extends BaseAdapter<
|
|
78
|
+
export class VesuMultiplyAdapter extends BaseAdapter<
|
|
79
|
+
VesuDepositParams,
|
|
80
|
+
VesuWithdrawParams
|
|
81
|
+
> {
|
|
28
82
|
readonly config: VesuMultiplyAdapterConfig;
|
|
29
|
-
readonly
|
|
83
|
+
readonly _vesuAdapter: VesuAdapter;
|
|
30
84
|
readonly tokenMarketData: TokenMarketData;
|
|
31
|
-
|
|
85
|
+
readonly minimumVesuMovementAmount: number;
|
|
86
|
+
lastSwapPriceInfo: SwapPriceInfo | null = null;
|
|
87
|
+
maxSlippage: number = 0.002; // 0.2%
|
|
32
88
|
constructor(config: VesuMultiplyAdapterConfig) {
|
|
33
89
|
super(config, VesuMultiplyAdapter.name, Protocols.VESU);
|
|
34
90
|
this.config = config;
|
|
35
|
-
this.
|
|
91
|
+
this._vesuAdapter = new VesuAdapter({
|
|
36
92
|
poolId: config.poolId,
|
|
37
93
|
collateral: config.collateral,
|
|
38
94
|
debt: config.debt,
|
|
39
95
|
vaultAllocator: config.vaultAllocator,
|
|
40
|
-
id:
|
|
96
|
+
id: "",
|
|
41
97
|
});
|
|
42
|
-
this.
|
|
98
|
+
this.minimumVesuMovementAmount = config.minimumVesuMovementAmount ?? 5; //5 usdc
|
|
99
|
+
this.tokenMarketData = new TokenMarketData(
|
|
100
|
+
this.config.pricer,
|
|
101
|
+
this.config.networkConfig
|
|
102
|
+
);
|
|
103
|
+
this.config.maxSlippage = config.maxSlippage ?? 0.002; // 0.2%
|
|
104
|
+
this.maxSlippage = config.maxSlippage ?? 0.002; // 0.2%
|
|
43
105
|
}
|
|
44
106
|
|
|
45
|
-
|
|
46
|
-
const CACHE_KEY = `apy_${this.config.poolId.address}_${supportedPosition.asset.symbol}`;
|
|
47
|
-
const cacheData = this.getCache<PositionAPY>(CACHE_KEY);
|
|
48
|
-
console.log(`${VesuMultiplyAdapter.name}::getAPY cacheData: ${JSON.stringify(cacheData)}`, this.vesuAdapter.config.poolId.shortString(), this.vesuAdapter.config.collateral.symbol, this.vesuAdapter.config.debt.symbol);
|
|
49
|
-
if (cacheData) {
|
|
50
|
-
return cacheData;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
// Get Vesu pools to find APY for the asset
|
|
54
|
-
const allVesuPools = await VesuAdapter.getVesuPools();
|
|
55
|
-
const asset = supportedPosition.asset;
|
|
56
|
-
const pool = allVesuPools.pools.find(p => this.vesuAdapter.config.poolId.eqString(num.getHexString(p.id)));
|
|
57
|
-
if (!pool) {
|
|
58
|
-
logger.warn(`VesuMultiplyAdapter: Pool not found for token ${asset.symbol}`);
|
|
59
|
-
return {
|
|
60
|
-
apy: 0,
|
|
61
|
-
type: APYType.BASE
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
// Find the asset stats for our token
|
|
65
|
-
const assetStats = pool.assets.find((a: any) =>
|
|
66
|
-
a.symbol.toLowerCase() === asset.symbol.toLowerCase()
|
|
67
|
-
)?.stats;
|
|
68
|
-
|
|
69
|
-
if (!assetStats) {
|
|
70
|
-
logger.warn(`VesuMultiplyAdapter: Asset stats not found for token ${asset.symbol}`);
|
|
71
|
-
return {
|
|
72
|
-
apy: 0,
|
|
73
|
-
type: APYType.BASE
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
// Get appropriate APY based on position type
|
|
77
|
-
let apy = 0;
|
|
78
|
-
if (supportedPosition.isDebt) {
|
|
79
|
-
// For debt positions, use borrow APY
|
|
80
|
-
apy = Number(assetStats.borrowApr?.value || 0) / 1e18;
|
|
81
|
-
|
|
82
|
-
// todo
|
|
83
|
-
// Account for rewards on debt token
|
|
84
|
-
} else {
|
|
85
|
-
// For collateral positions, use supply APY
|
|
86
|
-
const isAssetBTC = asset.symbol.toLowerCase().includes("btc");
|
|
87
|
-
const baseAPY = Number(isAssetBTC ? assetStats.btcFiSupplyApr?.value + assetStats.supplyApy?.value : assetStats.supplyApy?.value || 0) / 1e18;
|
|
88
|
-
|
|
89
|
-
// account for reward yield (like STRK rewards)
|
|
90
|
-
const rewardAPY = Number(assetStats.defiSpringSupplyApr?.value || "0") / 1e18;
|
|
91
|
-
|
|
92
|
-
// account for base yield of LST
|
|
93
|
-
const isSupported = this.tokenMarketData.isAPYSupported(asset);
|
|
94
|
-
apy = baseAPY + rewardAPY;
|
|
95
|
-
if (isSupported) {
|
|
96
|
-
const tokenAPY = await this.tokenMarketData.getAPY(asset);
|
|
97
|
-
apy += tokenAPY;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const result = {
|
|
101
|
-
apy,
|
|
102
|
-
type: supportedPosition.isDebt ? APYType.BASE : APYType.BASE
|
|
103
|
-
};
|
|
107
|
+
// ─── Shared Helpers ──────────────────────────────────────────────────────────
|
|
104
108
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
private _getMultiplyContract(): {
|
|
110
|
+
contract: Contract;
|
|
111
|
+
vesuMultiply: ContractAddr;
|
|
112
|
+
vesuSingleton: ContractAddr;
|
|
113
|
+
isV2: boolean;
|
|
114
|
+
} {
|
|
115
|
+
const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(
|
|
116
|
+
this.config.poolId
|
|
117
|
+
);
|
|
118
|
+
const vesuMultiply = isV2
|
|
119
|
+
? this._vesuAdapter.VESU_WITHDRAW_SWAP_FIXED_MULTIPLIER
|
|
120
|
+
: this._vesuAdapter.VESU_MULTIPLY_V1;
|
|
121
|
+
const contract = new Contract({
|
|
122
|
+
abi: VesuMultiplyAbi,
|
|
123
|
+
address: vesuMultiply.address,
|
|
124
|
+
providerOrAccount: this.config.networkConfig.provider,
|
|
125
|
+
});
|
|
126
|
+
return { contract, vesuMultiply, vesuSingleton, isV2 };
|
|
115
127
|
}
|
|
116
128
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
private _buildDelegationWrappedCalls(
|
|
130
|
+
preCalls: ManageCall[],
|
|
131
|
+
modifyLeverCalldata: bigint[],
|
|
132
|
+
proofReadableIds: {
|
|
133
|
+
delegationOn: string;
|
|
134
|
+
modifyLever: string;
|
|
135
|
+
delegationOff: string;
|
|
136
|
+
},
|
|
137
|
+
): ManageCall[] {
|
|
138
|
+
const { vesuMultiply, vesuSingleton, isV2 } = this._getMultiplyContract();
|
|
123
139
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
const delegationOn: ManageCall = {
|
|
141
|
+
proofReadableId: proofReadableIds.delegationOn,
|
|
142
|
+
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
143
|
+
call: {
|
|
144
|
+
contractAddress: vesuSingleton,
|
|
145
|
+
selector: hash.getSelectorFromName("modify_delegation"),
|
|
146
|
+
calldata: isV2
|
|
147
|
+
? [vesuMultiply.toBigInt(), BigInt(1)]
|
|
148
|
+
: [this.config.poolId.toBigInt(), vesuMultiply.toBigInt(), BigInt(1)],
|
|
149
|
+
},
|
|
150
|
+
};
|
|
135
151
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
152
|
+
const modifyLever: ManageCall = {
|
|
153
|
+
proofReadableId: proofReadableIds.modifyLever,
|
|
154
|
+
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
155
|
+
call: {
|
|
156
|
+
contractAddress: vesuMultiply,
|
|
157
|
+
selector: hash.getSelectorFromName("modify_lever"),
|
|
158
|
+
calldata: modifyLeverCalldata,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
143
161
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
162
|
+
const delegationOff: ManageCall = {
|
|
163
|
+
proofReadableId: proofReadableIds.delegationOff,
|
|
164
|
+
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
165
|
+
call: {
|
|
166
|
+
contractAddress: vesuSingleton,
|
|
167
|
+
selector: hash.getSelectorFromName("modify_delegation"),
|
|
168
|
+
calldata: isV2
|
|
169
|
+
? [vesuMultiply.toBigInt(), BigInt(0)]
|
|
170
|
+
: [this.config.poolId.toBigInt(), vesuMultiply.toBigInt(), BigInt(0)],
|
|
171
|
+
},
|
|
172
|
+
};
|
|
148
173
|
|
|
149
|
-
|
|
150
|
-
return position;
|
|
151
|
-
} catch (error) {
|
|
152
|
-
logger.error(`VesuMultiplyAdapter: Error getting position for ${supportedPosition.asset.symbol}:`, error);
|
|
153
|
-
// return new Web3Number('0', supportedPosition.asset.decimals);
|
|
154
|
-
throw error;
|
|
155
|
-
}
|
|
174
|
+
return [...preCalls, delegationOn, modifyLever, delegationOff];
|
|
156
175
|
}
|
|
157
176
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
177
|
+
private _getDepositProofReadableIds() {
|
|
178
|
+
const collateral = this.config.collateral;
|
|
179
|
+
const debt = this.config.debt;
|
|
180
|
+
return {
|
|
181
|
+
approve: `amc_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
182
|
+
delegationOn: `sd1_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
183
|
+
modifyLever: `vm_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
184
|
+
delegationOff: `sd2_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
185
|
+
};
|
|
163
186
|
}
|
|
164
187
|
|
|
165
|
-
|
|
188
|
+
private _getWithdrawProofReadableIds() {
|
|
166
189
|
const collateral = this.config.collateral;
|
|
167
190
|
const debt = this.config.debt;
|
|
191
|
+
return {
|
|
192
|
+
delegationOn: `sdow_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
193
|
+
modifyLever: `vmw_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
194
|
+
delegationOff: `sdofw_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
168
197
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// Calculate max borrowable amount
|
|
183
|
-
const maxBorrowableAPY = await this.maxBorrowableAPY();
|
|
184
|
-
const maxBorrowable = await this.vesuAdapter.getMaxBorrowableByInterestRate(
|
|
185
|
-
this.config.networkConfig,
|
|
186
|
-
debt,
|
|
187
|
-
maxBorrowableAPY
|
|
188
|
-
);
|
|
189
|
-
logger.verbose(`VesuMultiplyAdapter: Max borrowable: ${maxBorrowable.toNumber()}`);
|
|
190
|
-
const debtCap = await this.vesuAdapter.getDebtCap(this.config.networkConfig);
|
|
191
|
-
logger.verbose(`VesuMultiplyAdapter: Debt cap: ${debtCap.toNumber()}`);
|
|
192
|
-
const actualMaxBorrowable = maxBorrowable.minimum(debtCap);
|
|
193
|
-
logger.verbose(`VesuMultiplyAdapter: Actual max borrowable: ${actualMaxBorrowable.toNumber()}`);
|
|
194
|
-
|
|
195
|
-
// Calculate max collateral that can be deposited based on LTV
|
|
196
|
-
const maxLTV = await this.vesuAdapter.getLTVConfig(this.config.networkConfig);
|
|
197
|
-
const collateralPrice = await this.config.pricer.getPrice(collateral.symbol);
|
|
198
|
-
if (collateralPrice.price === 0) {
|
|
199
|
-
throw new Error('Collateral price is 0');
|
|
200
|
-
}
|
|
201
|
-
const debtPrice = await this.config.pricer.getPrice(debt.symbol);
|
|
202
|
-
if (debtPrice.price === 0) {
|
|
203
|
-
throw new Error('Debt price is 0');
|
|
204
|
-
}
|
|
205
|
-
const maxCollateralFromDebt = HealthFactorMath.getMinCollateralRequiredOnLooping(
|
|
206
|
-
actualMaxBorrowable,
|
|
207
|
-
debtPrice.price,
|
|
208
|
-
this.config.targetHealthFactor,
|
|
209
|
-
maxLTV,
|
|
210
|
-
collateralPrice.price,
|
|
211
|
-
collateral
|
|
212
|
-
)
|
|
198
|
+
private _buildZeroAmountSwapsWithWeights(
|
|
199
|
+
quote: EkuboQuote,
|
|
200
|
+
token: TokenInfo,
|
|
201
|
+
reverseRoutes = false,
|
|
202
|
+
): { swaps: Swap[]; weights: Web3Number[] } {
|
|
203
|
+
const swaps: Swap[] = quote.splits.map((split) => {
|
|
204
|
+
let sellToken = token.address;
|
|
205
|
+
const routeNodes = split.route.map((_route: any) => {
|
|
206
|
+
// switch to other for next route.
|
|
207
|
+
let isSellToken0 = sellToken.eqString(_route.pool_key.token0);
|
|
208
|
+
isSellToken0 = reverseRoutes ? !isSellToken0 : isSellToken0;
|
|
209
|
+
sellToken = isSellToken0 ? ContractAddr.from(_route.pool_key.token1) : ContractAddr.from(_route.pool_key.token0);
|
|
213
210
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
211
|
+
return {
|
|
212
|
+
pool_key: {
|
|
213
|
+
token0: ContractAddr.from(_route.pool_key.token0),
|
|
214
|
+
token1: ContractAddr.from(_route.pool_key.token1),
|
|
215
|
+
fee: _route.pool_key.fee,
|
|
216
|
+
tick_spacing: _route.pool_key.tick_spacing.toString(),
|
|
217
|
+
extension: _route.pool_key.extension,
|
|
218
|
+
},
|
|
219
|
+
sqrt_ratio_limit: isSellToken0 ? Web3Number.fromWei(MIN_SQRT_RATIO_LIMIT.toString(), 18) : Web3Number.fromWei(MAX_SQRT_RATIO_LIMIT.toString(), 18),
|
|
220
|
+
skip_ahead: Web3Number.fromWei(_route.skip_ahead, 0),
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
// incorrect logic, hence commented. Ekubo already returns routes in correct order.
|
|
224
|
+
// if (reverseRoutes) {
|
|
225
|
+
// routeNodes.reverse();
|
|
226
|
+
// }
|
|
225
227
|
return {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
apy: {
|
|
231
|
-
apy: netAPY,
|
|
232
|
-
type: APYType.BASE
|
|
228
|
+
route: routeNodes,
|
|
229
|
+
token_amount: {
|
|
230
|
+
token: token.address,
|
|
231
|
+
amount: Web3Number.fromWei(0, token.decimals),
|
|
233
232
|
},
|
|
234
|
-
protocol: this.protocol
|
|
235
233
|
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const totalSpecifiedBig = quote.splits.reduce(
|
|
237
|
+
(sum: bigint, s: any) => sum + BigInt(Math.abs(Number(s.amount_specified))),
|
|
238
|
+
0n
|
|
239
|
+
);
|
|
240
|
+
const weights: Web3Number[] = [];
|
|
241
|
+
let allocatedWeight = 0n;
|
|
242
|
+
for (let i = 0; i < quote.splits.length; i++) {
|
|
243
|
+
const split = quote.splits[i];
|
|
244
|
+
let weight: bigint;
|
|
245
|
+
if (i === quote.splits.length - 1) {
|
|
246
|
+
weight = SCALE_128 - allocatedWeight;
|
|
247
|
+
} else if (totalSpecifiedBig > 0n) {
|
|
248
|
+
const splitAbs = BigInt(Math.abs(Number(split.amount_specified)));
|
|
249
|
+
weight = (splitAbs * SCALE_128) / totalSpecifiedBig;
|
|
250
|
+
} else {
|
|
251
|
+
weight = SCALE_128;
|
|
252
|
+
}
|
|
253
|
+
allocatedWeight += weight;
|
|
254
|
+
weights.push(Web3Number.fromWei(weight.toString(), 0));
|
|
239
255
|
}
|
|
256
|
+
|
|
257
|
+
return { swaps, weights };
|
|
240
258
|
}
|
|
241
259
|
|
|
242
|
-
async
|
|
243
|
-
|
|
244
|
-
|
|
260
|
+
private async _fetchPositionAndPrices(): Promise<{
|
|
261
|
+
existingCollateralInfo: any;
|
|
262
|
+
existingDebtInfo: any;
|
|
263
|
+
collateralisation: any;
|
|
264
|
+
collateralPrice: number;
|
|
265
|
+
debtPrice: number;
|
|
266
|
+
ekuboQuoter: EkuboQuoter;
|
|
267
|
+
}> {
|
|
268
|
+
this._vesuAdapter.networkConfig = this.config.networkConfig;
|
|
269
|
+
this._vesuAdapter.pricer = this.config.pricer;
|
|
245
270
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (!collateralPosition || !debtPosition) {
|
|
256
|
-
throw new Error('Could not find current positions');
|
|
257
|
-
}
|
|
271
|
+
const existingPositions = await this._vesuAdapter.getPositions(
|
|
272
|
+
this.config.networkConfig
|
|
273
|
+
);
|
|
274
|
+
const collateralisation = await this._vesuAdapter.getCollateralization(
|
|
275
|
+
this.config.networkConfig
|
|
276
|
+
);
|
|
277
|
+
const existingCollateralInfo = existingPositions[0];
|
|
278
|
+
const existingDebtInfo = existingPositions[1];
|
|
258
279
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
280
|
+
const collateralPrice =
|
|
281
|
+
collateralisation[0].usdValue > 0
|
|
282
|
+
? collateralisation[0].usdValue /
|
|
283
|
+
existingCollateralInfo.amount.toNumber()
|
|
284
|
+
: (await this.config.pricer.getPrice(this.config.collateral.symbol))
|
|
285
|
+
.price;
|
|
286
|
+
const debtPrice =
|
|
287
|
+
collateralisation[1].usdValue > 0
|
|
288
|
+
? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber()
|
|
289
|
+
: (await this.config.pricer.getPrice(this.config.debt.symbol)).price;
|
|
290
|
+
|
|
291
|
+
const ekuboQuoter = new EkuboQuoter(
|
|
292
|
+
this.config.networkConfig,
|
|
293
|
+
this.config.pricer
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
existingCollateralInfo,
|
|
298
|
+
existingDebtInfo,
|
|
299
|
+
collateralisation,
|
|
300
|
+
collateralPrice,
|
|
301
|
+
debtPrice,
|
|
302
|
+
ekuboQuoter,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private _computeTargetDebtDelta(
|
|
307
|
+
addedCollateral: Web3Number,
|
|
308
|
+
existingCollateral: Web3Number,
|
|
309
|
+
existingDebt: Web3Number,
|
|
310
|
+
collateralPrice: number,
|
|
311
|
+
debtPrice: number,
|
|
312
|
+
ltv: number,
|
|
313
|
+
dexPrice: number
|
|
314
|
+
): Web3Number {
|
|
315
|
+
// target hf = (((collateral + addedCollateral) * collateralPrice + X)) * ltv / (debt * debtPrice + X)
|
|
316
|
+
// => X = (((collateral + addedCollateral) * collateralPrice * ltv) - (debt * debtPrice * target hf)) / (target hf - ltv)
|
|
317
|
+
const numeratorPart1 = existingCollateral
|
|
318
|
+
.plus(addedCollateral)
|
|
319
|
+
.multipliedBy(collateralPrice)
|
|
320
|
+
.multipliedBy(ltv);
|
|
321
|
+
const numeratorPart2 = existingDebt
|
|
322
|
+
.multipliedBy(debtPrice)
|
|
323
|
+
.multipliedBy(this.config.targetHealthFactor);
|
|
324
|
+
const denominatorPart = this.config.targetHealthFactor - ltv / dexPrice;
|
|
325
|
+
const x_debt_usd = numeratorPart1
|
|
326
|
+
.minus(numeratorPart2)
|
|
327
|
+
.dividedBy(denominatorPart);
|
|
328
|
+
|
|
329
|
+
return new Web3Number(
|
|
330
|
+
x_debt_usd.dividedBy(debtPrice).toFixed(this.config.debt.decimals),
|
|
331
|
+
this.config.debt.decimals
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private _getPositionCommonContext(): VesuPositionCommonContext {
|
|
336
|
+
return {
|
|
337
|
+
adapterName: VesuMultiplyAdapter.name,
|
|
338
|
+
protocol: this.protocol,
|
|
339
|
+
poolId: this.config.poolId,
|
|
340
|
+
collateral: this.config.collateral,
|
|
341
|
+
debt: this.config.debt,
|
|
342
|
+
networkConfig: this.config.networkConfig,
|
|
343
|
+
pricer: this.config.pricer,
|
|
344
|
+
vesuAdapter: this._vesuAdapter,
|
|
345
|
+
tokenMarketData: this.tokenMarketData,
|
|
346
|
+
targetHealthFactor: this.config.targetHealthFactor,
|
|
347
|
+
getCache: this.getCache.bind(this),
|
|
348
|
+
setCache: this.setCache.bind(this),
|
|
349
|
+
getUSDValue: this.getUSDValue.bind(this),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ─── APY / Position / Max Deposit/Withdraw ─────────────────────────────────
|
|
354
|
+
|
|
355
|
+
protected async getAPY(
|
|
356
|
+
supportedPosition: SupportedPosition
|
|
357
|
+
): Promise<PositionAPY> {
|
|
358
|
+
// always add vesu modify position adapter, which shall
|
|
359
|
+
// return correct apy
|
|
360
|
+
return {
|
|
361
|
+
apy: 0,
|
|
362
|
+
type: APYType.BASE,
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
protected async getPosition(
|
|
367
|
+
supportedPosition: SupportedPosition
|
|
368
|
+
): Promise<PositionAmount | null> {
|
|
369
|
+
// always add vesu modify position adapter, which shall
|
|
370
|
+
// return correct position amount
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async maxBorrowableAPY(): Promise<number> {
|
|
375
|
+
// always add vesu modify position adapter, which shall
|
|
376
|
+
// return correct max borrowable apy
|
|
377
|
+
return 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async maxDeposit(amount?: Web3Number): Promise<PositionInfo> {
|
|
381
|
+
// always add vesu modify position adapter, which shall
|
|
382
|
+
// return correct max deposit
|
|
383
|
+
return {
|
|
384
|
+
tokenInfo: this.config.baseToken,
|
|
385
|
+
amount: new Web3Number(0, this.config.baseToken.decimals),
|
|
386
|
+
usdValue: 0,
|
|
387
|
+
remarks: "Max deposit",
|
|
388
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
389
|
+
protocol: this.protocol,
|
|
286
390
|
}
|
|
287
391
|
}
|
|
288
392
|
|
|
393
|
+
async maxWithdraw(): Promise<PositionInfo> {
|
|
394
|
+
// always add vesu modify position adapter, which shall
|
|
395
|
+
// return correct max withdraw
|
|
396
|
+
return {
|
|
397
|
+
tokenInfo: this.config.baseToken,
|
|
398
|
+
amount: new Web3Number(0, this.config.baseToken.decimals),
|
|
399
|
+
usdValue: 0,
|
|
400
|
+
remarks: "Max withdraw",
|
|
401
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
402
|
+
protocol: this.protocol,
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ─── Leaf Configuration ────────────────────────────────────────────────────
|
|
407
|
+
|
|
289
408
|
protected _getDepositLeaf(): {
|
|
290
|
-
target: ContractAddr
|
|
291
|
-
method: string
|
|
292
|
-
packedArguments: bigint[]
|
|
293
|
-
sanitizer: ContractAddr
|
|
294
|
-
id: string
|
|
409
|
+
target: ContractAddr;
|
|
410
|
+
method: string;
|
|
411
|
+
packedArguments: bigint[];
|
|
412
|
+
sanitizer: ContractAddr;
|
|
413
|
+
id: string;
|
|
295
414
|
}[] {
|
|
296
415
|
const collateral = this.config.collateral;
|
|
297
416
|
const debt = this.config.debt;
|
|
298
|
-
const {
|
|
299
|
-
const
|
|
417
|
+
const { vesuMultiply, vesuSingleton, isV2 } = this._getMultiplyContract();
|
|
418
|
+
const token = debt;
|
|
419
|
+
const idSuffix = `${collateral.symbol}_${debt.symbol}`;
|
|
420
|
+
const idApprovePrefix = "amc";
|
|
421
|
+
const idDelegOnPrefix = "sd1";
|
|
422
|
+
const idMultiplyPrefix = "vm";
|
|
423
|
+
const idDelegOffPrefix = "sd2";
|
|
300
424
|
|
|
301
425
|
return [
|
|
302
|
-
// Approval step for collateral
|
|
303
426
|
{
|
|
304
|
-
target:
|
|
305
|
-
method:
|
|
306
|
-
packedArguments: [
|
|
307
|
-
vesuMultiply.toBigInt(), // spender
|
|
308
|
-
],
|
|
427
|
+
target: token.address,
|
|
428
|
+
method: "approve",
|
|
429
|
+
packedArguments: [vesuMultiply.toBigInt()],
|
|
309
430
|
sanitizer: SIMPLE_SANITIZER,
|
|
310
|
-
|
|
311
|
-
id: `amc_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
|
|
431
|
+
id: `${idApprovePrefix}_${this.config.poolId.shortString()}_${idSuffix}`,
|
|
312
432
|
},
|
|
313
|
-
// Switch delegation on
|
|
314
433
|
{
|
|
315
434
|
target: vesuSingleton,
|
|
316
|
-
method:
|
|
317
|
-
packedArguments: isV2
|
|
318
|
-
vesuMultiply.toBigInt()
|
|
319
|
-
|
|
320
|
-
this.config.poolId.toBigInt(),
|
|
321
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
322
|
-
],
|
|
435
|
+
method: "modify_delegation",
|
|
436
|
+
packedArguments: isV2
|
|
437
|
+
? [vesuMultiply.toBigInt()]
|
|
438
|
+
: [this.config.poolId.toBigInt(), vesuMultiply.toBigInt()],
|
|
323
439
|
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
324
|
-
|
|
325
|
-
id: `sd1_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
|
|
440
|
+
id: `${idDelegOnPrefix}_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
326
441
|
},
|
|
327
|
-
// Vesu multiply call
|
|
328
442
|
{
|
|
329
443
|
target: vesuMultiply,
|
|
330
|
-
method:
|
|
444
|
+
method: "modify_lever",
|
|
331
445
|
packedArguments: [
|
|
332
446
|
this.config.poolId.toBigInt(),
|
|
333
447
|
collateral.address.toBigInt(),
|
|
@@ -335,57 +449,44 @@ export class VesuMultiplyAdapter extends BaseAdapter<DepositParams, WithdrawPara
|
|
|
335
449
|
this.config.vaultAllocator.toBigInt(),
|
|
336
450
|
],
|
|
337
451
|
sanitizer: SIMPLE_SANITIZER_V2,
|
|
338
|
-
|
|
339
|
-
id: `vm_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
|
|
452
|
+
id: `${idMultiplyPrefix}_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
340
453
|
},
|
|
341
|
-
// Switch delegation off
|
|
342
454
|
{
|
|
343
455
|
target: vesuSingleton,
|
|
344
|
-
method:
|
|
345
|
-
packedArguments: isV2
|
|
346
|
-
vesuMultiply.toBigInt()
|
|
347
|
-
|
|
348
|
-
this.config.poolId.toBigInt(),
|
|
349
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
350
|
-
],
|
|
456
|
+
method: "modify_delegation",
|
|
457
|
+
packedArguments: isV2
|
|
458
|
+
? [vesuMultiply.toBigInt()]
|
|
459
|
+
: [this.config.poolId.toBigInt(), vesuMultiply.toBigInt()],
|
|
351
460
|
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
461
|
+
id: `${idDelegOffPrefix}_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
462
|
+
},
|
|
355
463
|
];
|
|
356
464
|
}
|
|
357
465
|
|
|
358
466
|
protected _getWithdrawLeaf(): {
|
|
359
|
-
target: ContractAddr
|
|
360
|
-
method: string
|
|
361
|
-
packedArguments: bigint[]
|
|
362
|
-
sanitizer: ContractAddr
|
|
363
|
-
id: string
|
|
467
|
+
target: ContractAddr;
|
|
468
|
+
method: string;
|
|
469
|
+
packedArguments: bigint[];
|
|
470
|
+
sanitizer: ContractAddr;
|
|
471
|
+
id: string;
|
|
364
472
|
}[] {
|
|
365
|
-
const {
|
|
366
|
-
const vesuMultiply = isV2 ? this.vesuAdapter.VESU_MULTIPLY : this.vesuAdapter.VESU_MULTIPLY_V1;
|
|
473
|
+
const { vesuMultiply, vesuSingleton, isV2 } = this._getMultiplyContract();
|
|
367
474
|
const collateral = this.config.collateral;
|
|
368
475
|
const debt = this.config.debt;
|
|
369
476
|
|
|
370
477
|
return [
|
|
371
|
-
// Switch delegation on
|
|
372
478
|
{
|
|
373
479
|
target: vesuSingleton,
|
|
374
|
-
method:
|
|
375
|
-
packedArguments: isV2
|
|
376
|
-
vesuMultiply.toBigInt()
|
|
377
|
-
|
|
378
|
-
this.config.poolId.toBigInt(),
|
|
379
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
380
|
-
],
|
|
480
|
+
method: "modify_delegation",
|
|
481
|
+
packedArguments: isV2
|
|
482
|
+
? [vesuMultiply.toBigInt()]
|
|
483
|
+
: [this.config.poolId.toBigInt(), vesuMultiply.toBigInt()],
|
|
381
484
|
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
382
|
-
|
|
383
|
-
id: `sdow_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
|
|
485
|
+
id: `sdow_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
384
486
|
},
|
|
385
|
-
// Vesu multiply call
|
|
386
487
|
{
|
|
387
488
|
target: vesuMultiply,
|
|
388
|
-
method:
|
|
489
|
+
method: "modify_lever",
|
|
389
490
|
packedArguments: [
|
|
390
491
|
this.config.poolId.toBigInt(),
|
|
391
492
|
this.config.collateral.address.toBigInt(),
|
|
@@ -393,531 +494,793 @@ export class VesuMultiplyAdapter extends BaseAdapter<DepositParams, WithdrawPara
|
|
|
393
494
|
this.config.vaultAllocator.toBigInt(),
|
|
394
495
|
],
|
|
395
496
|
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
396
|
-
|
|
397
|
-
id: `vmw_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
|
|
497
|
+
id: `vmw_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
398
498
|
},
|
|
399
|
-
// Switch delegation off
|
|
400
499
|
{
|
|
401
500
|
target: vesuSingleton,
|
|
402
|
-
method:
|
|
403
|
-
packedArguments: isV2
|
|
404
|
-
vesuMultiply.toBigInt()
|
|
405
|
-
|
|
406
|
-
this.config.poolId.toBigInt(),
|
|
407
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
408
|
-
],
|
|
501
|
+
method: "modify_delegation",
|
|
502
|
+
packedArguments: isV2
|
|
503
|
+
? [vesuMultiply.toBigInt()]
|
|
504
|
+
: [this.config.poolId.toBigInt(), vesuMultiply.toBigInt()],
|
|
409
505
|
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
}
|
|
506
|
+
id: `sdofw_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`,
|
|
507
|
+
},
|
|
413
508
|
];
|
|
414
509
|
}
|
|
415
510
|
|
|
416
|
-
|
|
511
|
+
// ─── Leaf Adapters ─────────────────────────────────────────────────────────
|
|
512
|
+
|
|
513
|
+
getDepositAdapter(approveToken?: TokenInfo): AdapterLeafType<VesuDepositParams> {
|
|
514
|
+
throw new Error("getDepositAdapter::Not implemented");
|
|
417
515
|
const leafConfigs = this._getDepositLeaf();
|
|
418
|
-
const leaves = leafConfigs.map(config => {
|
|
516
|
+
const leaves = leafConfigs.map((config) => {
|
|
419
517
|
const { target, method, packedArguments, sanitizer, id } = config;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
packedArguments
|
|
425
|
-
}, sanitizer);
|
|
426
|
-
return leaf;
|
|
518
|
+
return this.constructSimpleLeafData(
|
|
519
|
+
{ id, target, method, packedArguments },
|
|
520
|
+
sanitizer
|
|
521
|
+
);
|
|
427
522
|
});
|
|
428
|
-
return {
|
|
523
|
+
return {
|
|
524
|
+
leaves,
|
|
525
|
+
callConstructor: this.getDepositCall.bind(
|
|
526
|
+
this
|
|
527
|
+
) as unknown as GenerateCallFn<VesuDepositParams>,
|
|
528
|
+
};
|
|
429
529
|
}
|
|
430
530
|
|
|
431
|
-
getWithdrawAdapter(): AdapterLeafType<
|
|
531
|
+
getWithdrawAdapter(): AdapterLeafType<VesuWithdrawParams> {
|
|
532
|
+
throw new Error("getWithdrawAdapter::Dont think this fn is used anywhere");
|
|
432
533
|
const leafConfigs = this._getWithdrawLeaf();
|
|
433
|
-
const leaves = leafConfigs.map(config => {
|
|
534
|
+
const leaves = leafConfigs.map((config) => {
|
|
434
535
|
const { target, method, packedArguments, sanitizer, id } = config;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
packedArguments
|
|
440
|
-
}, sanitizer);
|
|
441
|
-
return leaf;
|
|
536
|
+
return this.constructSimpleLeafData(
|
|
537
|
+
{ id, target, method, packedArguments },
|
|
538
|
+
sanitizer
|
|
539
|
+
);
|
|
442
540
|
});
|
|
443
|
-
return {
|
|
541
|
+
return {
|
|
542
|
+
leaves,
|
|
543
|
+
callConstructor: this.getWithdrawCall.bind(
|
|
544
|
+
this
|
|
545
|
+
) as unknown as GenerateCallFn<VesuWithdrawParams>,
|
|
546
|
+
};
|
|
444
547
|
}
|
|
445
548
|
|
|
446
|
-
|
|
447
|
-
const collateral = this.config.collateral;
|
|
448
|
-
const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(this.config.poolId);
|
|
449
|
-
const vesuMultiply = isV2 ? this.vesuAdapter.VESU_MULTIPLY : this.vesuAdapter.VESU_MULTIPLY_V1;
|
|
549
|
+
// ─── Public Call Builders ──────────────────────────────────────────────────
|
|
450
550
|
|
|
451
|
-
|
|
551
|
+
async getDepositCall(params: VesuDepositParams): Promise<ManageCall[]> {
|
|
552
|
+
const { calldata, approveToken, approveAmount } = await this._getIncreaseCalldata(params);
|
|
553
|
+
const { vesuMultiply } = this._getMultiplyContract();
|
|
554
|
+
const proofReadableIds = this._getDepositProofReadableIds();
|
|
452
555
|
|
|
453
|
-
return [
|
|
454
|
-
// Approval call
|
|
455
|
-
{
|
|
456
|
-
sanitizer: SIMPLE_SANITIZER,
|
|
457
|
-
call: {
|
|
458
|
-
contractAddress: collateral.address,
|
|
459
|
-
selector: hash.getSelectorFromName('approve'),
|
|
460
|
-
calldata: [
|
|
461
|
-
vesuMultiply.toBigInt(), // spender
|
|
462
|
-
toBigInt(uint256MarginAmount.low.toString()), // amount low
|
|
463
|
-
toBigInt(uint256MarginAmount.high.toString()), // amount high
|
|
464
|
-
]
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
// Switch delegation on
|
|
468
|
-
{
|
|
469
|
-
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
470
|
-
call: {
|
|
471
|
-
contractAddress: vesuSingleton,
|
|
472
|
-
selector: hash.getSelectorFromName('modify_delegation'),
|
|
473
|
-
calldata: isV2 ? [
|
|
474
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
475
|
-
BigInt(1), // delegation: true
|
|
476
|
-
] : [
|
|
477
|
-
this.config.poolId.toBigInt(),
|
|
478
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
479
|
-
BigInt(1), // delegation: true
|
|
480
|
-
]
|
|
481
|
-
}
|
|
482
|
-
},
|
|
483
|
-
// Vesu multiply call
|
|
484
|
-
{
|
|
485
|
-
sanitizer: SIMPLE_SANITIZER_V2,
|
|
486
|
-
call: {
|
|
487
|
-
contractAddress: vesuMultiply,
|
|
488
|
-
selector: hash.getSelectorFromName('modify_lever'),
|
|
489
|
-
calldata: await this.getMultiplyCallCalldata(params, true)
|
|
490
|
-
}
|
|
491
|
-
},
|
|
492
|
-
// Switch delegation off
|
|
493
|
-
{
|
|
494
|
-
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
495
|
-
call: {
|
|
496
|
-
contractAddress: vesuSingleton,
|
|
497
|
-
selector: hash.getSelectorFromName('modify_delegation'),
|
|
498
|
-
calldata: isV2 ? [
|
|
499
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
500
|
-
BigInt(0), // delegation: false
|
|
501
|
-
] : [
|
|
502
|
-
this.config.poolId.toBigInt(),
|
|
503
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
504
|
-
BigInt(0), // delegation: false
|
|
505
|
-
]
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
];
|
|
509
|
-
}
|
|
510
556
|
|
|
511
|
-
|
|
512
|
-
const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(this.config.poolId);
|
|
513
|
-
const vesuMultiply = isV2 ? this.vesuAdapter.VESU_MULTIPLY : this.vesuAdapter.VESU_MULTIPLY_V1;
|
|
557
|
+
const uint256Amount = uint256.bnToUint256(approveAmount.toWei());
|
|
514
558
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
this.config.poolId.toBigInt(),
|
|
527
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
528
|
-
BigInt(1), // delegation: true
|
|
529
|
-
]
|
|
530
|
-
}
|
|
531
|
-
},
|
|
532
|
-
// Vesu multiply call
|
|
533
|
-
{
|
|
534
|
-
sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
|
|
535
|
-
call: {
|
|
536
|
-
contractAddress: vesuMultiply,
|
|
537
|
-
selector: hash.getSelectorFromName('modify_lever'),
|
|
538
|
-
calldata: await this.getWithdrawalCalldata(params)
|
|
539
|
-
}
|
|
559
|
+
const approveCall: ManageCall = {
|
|
560
|
+
proofReadableId: proofReadableIds.approve,
|
|
561
|
+
sanitizer: SIMPLE_SANITIZER,
|
|
562
|
+
call: {
|
|
563
|
+
contractAddress: approveToken.address,
|
|
564
|
+
selector: hash.getSelectorFromName("approve"),
|
|
565
|
+
calldata: [
|
|
566
|
+
vesuMultiply.toBigInt(),
|
|
567
|
+
toBigInt(uint256Amount.low.toString()),
|
|
568
|
+
toBigInt(uint256Amount.high.toString()),
|
|
569
|
+
],
|
|
540
570
|
},
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
549
|
-
BigInt(0), // delegation: false
|
|
550
|
-
] : [
|
|
551
|
-
this.config.poolId.toBigInt(),
|
|
552
|
-
vesuMultiply.toBigInt(), // delegatee
|
|
553
|
-
BigInt(0), // delegation: false
|
|
554
|
-
]
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
];
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
return this._buildDelegationWrappedCalls([approveCall], calldata, {
|
|
574
|
+
delegationOn: proofReadableIds.delegationOn,
|
|
575
|
+
modifyLever: proofReadableIds.modifyLever,
|
|
576
|
+
delegationOff: proofReadableIds.delegationOff,
|
|
577
|
+
});
|
|
558
578
|
}
|
|
559
579
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
abi: VesuMultiplyAbi,
|
|
568
|
-
address: vesuMultiply.address,
|
|
569
|
-
providerOrAccount: this.config.networkConfig.provider
|
|
580
|
+
async getWithdrawCall(params: VesuWithdrawParams): Promise<ManageCall[]> {
|
|
581
|
+
const calldata = await this._getDecreaseCalldata(params);
|
|
582
|
+
const proofReadableIds = this._getWithdrawProofReadableIds();
|
|
583
|
+
return this._buildDelegationWrappedCalls([], calldata, {
|
|
584
|
+
delegationOn: proofReadableIds.delegationOn,
|
|
585
|
+
modifyLever: proofReadableIds.modifyLever,
|
|
586
|
+
delegationOff: proofReadableIds.delegationOff,
|
|
570
587
|
});
|
|
588
|
+
}
|
|
571
589
|
|
|
572
|
-
|
|
573
|
-
let leverSwap: Swap[] = [];
|
|
574
|
-
let leverSwapLimitAmount = Web3Number.fromWei(0, this.config.debt.decimals);
|
|
590
|
+
// ─── Consolidated Calldata Builders ────────────────────────────────────────
|
|
575
591
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const existingDebtInfo = existingPositions[1];
|
|
580
|
-
const isDexPriceRequired = existingDebtInfo.token.symbol !== "USDC";
|
|
581
|
-
logger.debug(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
582
|
-
existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
583
|
-
|
|
584
|
-
// - Prices as seen by Vesu contracts, ideal for HF math
|
|
585
|
-
// Price 1 is ok as fallback bcz that would relatively price the
|
|
586
|
-
// collateral and debt as equal.
|
|
587
|
-
const collateralPrice = collateralisation[0].usdValue > 0 ?
|
|
588
|
-
collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() :
|
|
589
|
-
(await this.config.pricer.getPrice(this.config.collateral.symbol)).price;
|
|
590
|
-
const debtPrice = collateralisation[1].usdValue > 0 ?
|
|
591
|
-
collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() :
|
|
592
|
-
(await this.config.pricer.getPrice(this.config.debt.symbol)).price;
|
|
593
|
-
logger.debug(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
|
|
594
|
-
|
|
595
|
-
const legLTV = await this.vesuAdapter.getLTVConfig(this.config.networkConfig);
|
|
596
|
-
const ekuboQuoter = new EkuboQuoter(this.config.networkConfig, this.config.pricer);
|
|
597
|
-
const dexPrice = isDexPriceRequired ? await ekuboQuoter.getDexPrice(this.config.collateral, this.config.debt, this.config.quoteAmountToFetchPrice) : 1;
|
|
598
|
-
logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall dexPrice: ${dexPrice}, ltv: ${legLTV}`);
|
|
599
|
-
|
|
600
|
-
// compute optimal amount of collateral and debt post addition/removal
|
|
601
|
-
// target hf = collateral * collateralPrice * ltv / debt * debtPrice
|
|
602
|
-
// assuming X to be the usd amount of debt borrowed or repaied (negative).
|
|
603
|
-
// target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv / (debt * debtPrice + X)
|
|
604
|
-
// => X * target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv - (debt * debtPrice * target hf)
|
|
605
|
-
// => X * (target hf - ltv)= ((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)
|
|
606
|
-
// => X = (((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)) / (target hf - ltv)
|
|
607
|
-
|
|
608
|
-
const addedCollateral = params.amount
|
|
609
|
-
.multipliedBy(isDeposit ? 1 : -1)
|
|
610
|
-
logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall addedCollateral: ${addedCollateral}`);
|
|
611
|
-
const numeratorPart1 = (existingCollateralInfo.amount.plus((addedCollateral))).multipliedBy(collateralPrice).multipliedBy(legLTV);
|
|
612
|
-
logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}`);
|
|
613
|
-
const numeratorPart2 = existingDebtInfo.amount.multipliedBy(debtPrice).multipliedBy(this.config.targetHealthFactor);
|
|
614
|
-
logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall numeratorPart2: ${numeratorPart2}`);
|
|
615
|
-
const denominatorPart = this.config.targetHealthFactor - (legLTV / dexPrice); // TODO Write reason for this. this dexPrice is some custom thing. this dexPrice is probably exchange rate (1 xWBTC in WBTC terms)
|
|
616
|
-
logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall denominatorPart: ${denominatorPart}`);
|
|
617
|
-
const x_debt_usd = numeratorPart1.minus(numeratorPart2).dividedBy(denominatorPart);
|
|
618
|
-
logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall x_debt_usd: ${x_debt_usd}`);
|
|
619
|
-
logger.debug(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}, numeratorPart2: ${numeratorPart2}, denominatorPart: ${denominatorPart}`);
|
|
620
|
-
|
|
621
|
-
// both in underlying
|
|
622
|
-
// debtAmount in debt units
|
|
623
|
-
let debtAmount = new Web3Number(x_debt_usd.dividedBy(debtPrice).toFixed(this.config.debt.decimals), this.config.debt.decimals);
|
|
624
|
-
const marginAmount = addedCollateral;
|
|
592
|
+
private async _getIncreaseCalldata(
|
|
593
|
+
params: VesuDepositParams
|
|
594
|
+
): Promise<{ calldata: bigint[]; approveToken: TokenInfo; approveAmount: Web3Number }> {
|
|
625
595
|
const collateralToken = this.config.collateral;
|
|
626
596
|
const debtToken = this.config.debt;
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const
|
|
597
|
+
const { contract: multiplyContract } = this._getMultiplyContract();
|
|
598
|
+
this.lastSwapPriceInfo = null;
|
|
599
|
+
|
|
600
|
+
const {
|
|
601
|
+
existingCollateralInfo,
|
|
602
|
+
existingDebtInfo,
|
|
603
|
+
collateralPrice,
|
|
604
|
+
debtPrice,
|
|
605
|
+
ekuboQuoter,
|
|
606
|
+
} = await this._fetchPositionAndPrices();
|
|
607
|
+
|
|
608
|
+
logger.verbose(
|
|
609
|
+
`${VesuMultiplyAdapter.name}::_getIncreaseCalldata params: ${JSON.stringify(
|
|
610
|
+
params
|
|
611
|
+
)}, collateral: ${collateralToken.symbol}, debt: ${debtToken.symbol}`
|
|
612
|
+
);
|
|
613
|
+
logger.debug(
|
|
614
|
+
`${VesuMultiplyAdapter.name}::_getIncreaseCalldata existingCollateralInfo: ${JSON.stringify(
|
|
615
|
+
existingCollateralInfo
|
|
616
|
+
)}, existingDebtInfo: ${JSON.stringify(existingDebtInfo)}`
|
|
617
|
+
);
|
|
618
|
+
logger.debug(
|
|
619
|
+
`${VesuMultiplyAdapter.name}::_getIncreaseCalldata collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
const legLTV = await this._vesuAdapter.getLTVConfig(
|
|
623
|
+
this.config.networkConfig
|
|
624
|
+
);
|
|
625
|
+
const isDexPriceRequired = debtToken.symbol !== "USDC";
|
|
626
|
+
const dexPrice = isDexPriceRequired
|
|
627
|
+
? await ekuboQuoter.getDexPrice(
|
|
628
|
+
collateralToken,
|
|
629
|
+
debtToken,
|
|
630
|
+
this.config.quoteAmountToFetchPrice
|
|
631
|
+
)
|
|
632
|
+
: 1;
|
|
633
|
+
logger.verbose(
|
|
634
|
+
`${VesuMultiplyAdapter.name}::_getIncreaseCalldata dexPrice: ${dexPrice}, ltv: ${legLTV}`
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
let marginSwap: Swap[] = [];
|
|
638
|
+
let marginSwapLimitAmount = Web3Number.fromWei(0, collateralToken.decimals);
|
|
639
|
+
let addedCollateral: Web3Number = params.amount;
|
|
640
|
+
let approveAmount: Web3Number = params.amount;
|
|
641
|
+
let aggregatedFromAmount = 0; // e.g. USDC
|
|
642
|
+
let aggregatedToAmount = 0; // e.g. BTC
|
|
643
|
+
let aggregatedFromSymbol: string = debtToken.symbol;
|
|
644
|
+
const aggregatedToSymbol = collateralToken.symbol;
|
|
645
|
+
let executedSwapCount = 0;
|
|
631
646
|
|
|
647
|
+
if (params.marginSwap) {
|
|
648
|
+
const marginToken = params.marginSwap.marginToken;
|
|
649
|
+
const requiredAmount = params.amount;
|
|
632
650
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
651
|
+
// bcz, last swap price is configured to be common between margin swap and lever swap,
|
|
652
|
+
// hence need same tokens in both
|
|
653
|
+
assert(marginToken.address.eq(debtToken.address), 'Margin token must be the same as debt token');
|
|
654
|
+
|
|
655
|
+
const marginSwapQuote = await ekuboQuoter.getQuoteExactOutput(
|
|
656
|
+
marginToken.address.address,
|
|
657
|
+
collateralToken.address.address,
|
|
658
|
+
requiredAmount
|
|
659
|
+
);
|
|
660
|
+
if (marginSwapQuote.price_impact > 0.01) {
|
|
661
|
+
throw new Error(
|
|
662
|
+
`VesuMultiplyAdapter: Margin swap price impact too high (${marginSwapQuote.price_impact})`
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
marginSwap = ekuboQuoter.getVesuMultiplyQuote(
|
|
667
|
+
marginSwapQuote,
|
|
668
|
+
marginToken,
|
|
669
|
+
collateralToken
|
|
670
|
+
);
|
|
671
|
+
const marginSwapInputAmount = Web3Number
|
|
672
|
+
.fromWei(marginSwapQuote.total_calculated, marginToken.decimals)
|
|
673
|
+
.abs()
|
|
674
|
+
.toNumber();
|
|
675
|
+
const marginSwapOutputAmount = requiredAmount.abs().toNumber();
|
|
676
|
+
aggregatedFromAmount += marginSwapInputAmount;
|
|
677
|
+
aggregatedToAmount += marginSwapOutputAmount;
|
|
678
|
+
executedSwapCount += 1;
|
|
679
|
+
|
|
680
|
+
approveAmount = Web3Number
|
|
681
|
+
.fromWei(marginSwapQuote.total_calculated, marginToken.decimals)
|
|
682
|
+
.multipliedBy(1 + this.maxSlippage)
|
|
683
|
+
.abs();
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
let debtAmount = this._computeTargetDebtDelta(
|
|
687
|
+
addedCollateral,
|
|
688
|
+
existingCollateralInfo.amount,
|
|
689
|
+
existingDebtInfo.amount,
|
|
690
|
+
collateralPrice,
|
|
691
|
+
debtPrice,
|
|
692
|
+
legLTV,
|
|
693
|
+
dexPrice
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
logger.info(
|
|
697
|
+
`${VesuMultiplyAdapter.name}::_getIncreaseCalldata debtAmount: ${debtAmount}, addedCollateral: ${addedCollateral}`
|
|
698
|
+
);
|
|
699
|
+
let leverSwap: Swap[] = [];
|
|
700
|
+
let leverSwapLimitAmount = Web3Number.fromWei(0, debtToken.decimals);
|
|
701
|
+
|
|
702
|
+
const isIncrease = debtAmount.greaterThanOrEqualTo(0);
|
|
703
|
+
if (!isIncrease && debtAmount.greaterThan(0)) {
|
|
641
704
|
debtAmount = Web3Number.fromWei(0, this.config.debt.decimals);
|
|
642
705
|
}
|
|
643
|
-
|
|
644
|
-
if (!debtAmount.isZero()) {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
706
|
+
|
|
707
|
+
if (!debtAmount.isZero() && debtAmount.greaterThan(0)) {
|
|
708
|
+
try {
|
|
709
|
+
let swapQuote: EkuboQuote;
|
|
710
|
+
if (params.leverSwap?.exactOutput) {
|
|
711
|
+
swapQuote = await ekuboQuoter.getQuoteExactOutput(
|
|
712
|
+
debtToken.address.address,
|
|
713
|
+
collateralToken.address.address,
|
|
714
|
+
params.leverSwap?.exactOutput?.abs()!
|
|
715
|
+
);
|
|
716
|
+
debtAmount = Web3Number.fromWei(swapQuote.total_calculated, debtToken.decimals).abs();
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
swapQuote = await ekuboQuoter.getQuoteExactInput(
|
|
651
720
|
debtToken.address.address,
|
|
652
|
-
|
|
721
|
+
collateralToken.address.address,
|
|
722
|
+
debtAmount.abs()
|
|
653
723
|
);
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
} else {
|
|
678
|
-
leverSwapLimitAmount = Web3Number.fromWei(0, this.config.debt.decimals);
|
|
679
|
-
}
|
|
724
|
+
const expectedOutputAmount = debtAmount.multipliedBy(debtPrice).dividedBy(collateralPrice);
|
|
725
|
+
expectedOutputAmount.decimals = collateralToken.decimals;
|
|
726
|
+
leverSwapLimitAmount = expectedOutputAmount.multipliedBy(1 - this.maxSlippage);
|
|
727
|
+
|
|
728
|
+
if (swapQuote.price_impact < 0.01) {
|
|
729
|
+
const quoteOutputAmount = Web3Number.fromWei(
|
|
730
|
+
swapQuote.total_calculated,
|
|
731
|
+
collateralToken.decimals
|
|
732
|
+
)
|
|
733
|
+
.abs()
|
|
734
|
+
.toNumber();
|
|
735
|
+
|
|
736
|
+
const inputAmt = debtAmount.abs().toNumber();
|
|
737
|
+
const outputAmt = quoteOutputAmount;
|
|
738
|
+
aggregatedFromAmount += inputAmt;
|
|
739
|
+
aggregatedToAmount += outputAmt;
|
|
740
|
+
executedSwapCount += 1;
|
|
741
|
+
|
|
742
|
+
leverSwap = ekuboQuoter.getVesuMultiplyQuote(
|
|
743
|
+
swapQuote,
|
|
744
|
+
debtToken,
|
|
745
|
+
collateralToken
|
|
746
|
+
);
|
|
680
747
|
await new Promise((resolve) => setTimeout(resolve, 10000));
|
|
681
|
-
//console.log("leverSwapLimitAmount", leverSwapLimitAmount);
|
|
682
748
|
} else {
|
|
683
|
-
throw new Error(
|
|
749
|
+
throw new Error(
|
|
750
|
+
`VesuMultiplyAdapter: Price impact too high (${swapQuote.price_impact}), skipping swap, debtAmount=${debtAmount.toNumber()}, collateralPrice=${collateralPrice}, debtPrice=${debtPrice}`
|
|
751
|
+
);
|
|
684
752
|
}
|
|
685
753
|
} catch (error) {
|
|
686
|
-
throw new Error(
|
|
754
|
+
throw new Error(
|
|
755
|
+
`VesuMultiplyAdapter: Failed to get swap quote: ${error}`
|
|
756
|
+
);
|
|
687
757
|
}
|
|
688
758
|
}
|
|
689
|
-
|
|
690
|
-
const multiplyParams = await this.getLeverParams(isIncrease, params, leverSwap, leverSwapLimitAmount);
|
|
691
|
-
const call = multiplyContract.populate('modify_lever', {
|
|
692
|
-
modify_lever_params: this.formatMultiplyParams(isIncrease, multiplyParams)
|
|
693
|
-
});
|
|
694
759
|
|
|
695
|
-
|
|
696
|
-
|
|
760
|
+
if (executedSwapCount > 0) {
|
|
761
|
+
this.lastSwapPriceInfo = {
|
|
762
|
+
source: "ekubo",
|
|
763
|
+
fromTokenSymbol: aggregatedFromSymbol ?? debtToken.symbol,
|
|
764
|
+
toTokenSymbol: aggregatedToSymbol,
|
|
765
|
+
fromAmount: aggregatedFromAmount,
|
|
766
|
+
toAmount: aggregatedToAmount,
|
|
767
|
+
effectivePrice: aggregatedToAmount !== 0 ? aggregatedFromAmount / aggregatedToAmount : 0,
|
|
768
|
+
};
|
|
769
|
+
logger.verbose(
|
|
770
|
+
`${VesuMultiplyAdapter.name}::_getIncreaseCalldata stored aggregated price info: ` +
|
|
771
|
+
`${aggregatedFromAmount} ${this.lastSwapPriceInfo.fromTokenSymbol} → ${aggregatedToAmount} ${aggregatedToSymbol}, ` +
|
|
772
|
+
`effectivePrice=${this.lastSwapPriceInfo.effectivePrice}, swaps=${executedSwapCount}`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
697
775
|
|
|
698
|
-
|
|
699
|
-
|
|
776
|
+
logger.verbose(
|
|
777
|
+
`${VesuMultiplyAdapter.name}::_getIncreaseCalldata leverSwapLimitAmount: ${leverSwapLimitAmount.toWei()}`
|
|
778
|
+
);
|
|
779
|
+
logger.verbose(
|
|
780
|
+
`${VesuMultiplyAdapter.name}::_getIncreaseCalldata approveAmount: ${approveAmount.toWei()}, marginSwapLimitAmount: ${marginSwapLimitAmount.toWei()}`
|
|
781
|
+
);
|
|
782
|
+
logger.verbose(
|
|
783
|
+
`${VesuMultiplyAdapter.name}::_getIncreaseCalldata marginSwap: ${JSON.stringify(marginSwap)}`
|
|
784
|
+
);
|
|
785
|
+
const multiplyParams: IncreaseLeverParams = {
|
|
700
786
|
user: this.config.vaultAllocator,
|
|
701
787
|
pool_id: this.config.poolId,
|
|
702
|
-
collateral_asset:
|
|
703
|
-
debt_asset:
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
788
|
+
collateral_asset: collateralToken.address,
|
|
789
|
+
debt_asset: debtToken.address,
|
|
790
|
+
add_margin: params.marginSwap
|
|
791
|
+
? Web3Number.fromWei(0, collateralToken.decimals)
|
|
792
|
+
: params.amount,
|
|
793
|
+
margin_swap: marginSwap,
|
|
794
|
+
margin_swap_limit_amount: params.marginSwap ? approveAmount : marginSwapLimitAmount,
|
|
708
795
|
lever_swap: leverSwap,
|
|
709
|
-
lever_swap_limit_amount: leverSwapLimitAmount
|
|
710
|
-
}
|
|
796
|
+
lever_swap_limit_amount: leverSwapLimitAmount,
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
const call = multiplyContract.populate("modify_lever", {
|
|
800
|
+
modify_lever_params: this.formatMultiplyParams(true, multiplyParams),
|
|
801
|
+
});
|
|
802
|
+
logger.debug(
|
|
803
|
+
`${VesuMultiplyAdapter.name}::_getIncreaseCalldata marginSwapCount=${marginSwap.length}, leverSwapCount=${leverSwap.length}`,
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
return {
|
|
807
|
+
calldata: call.calldata as bigint[],
|
|
808
|
+
approveToken: params.marginSwap?.marginToken ?? this.config.collateral,
|
|
809
|
+
approveAmount: approveAmount,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// private _setLastSwapPriceAsNoSwap(): void {
|
|
814
|
+
// this.lastSwapPriceInfo = {
|
|
815
|
+
// source: "no-swap",
|
|
816
|
+
// fromTokenSymbol: this.config.collateral.symbol,
|
|
817
|
+
// toTokenSymbol: this.config.debt.symbol,
|
|
818
|
+
// fromAmount: 0,
|
|
819
|
+
// toAmount: 0,
|
|
820
|
+
// effectivePrice: 0,
|
|
821
|
+
// };
|
|
822
|
+
// }
|
|
823
|
+
|
|
824
|
+
private async _buildDecreaseLikeCalldata(params: {
|
|
825
|
+
subMargin: Web3Number;
|
|
826
|
+
debtToRepayAbs: Web3Number;
|
|
827
|
+
existingCollateral: Web3Number;
|
|
828
|
+
closePosition: boolean;
|
|
829
|
+
outputToken?: TokenInfo;
|
|
830
|
+
collateralPrice: number;
|
|
831
|
+
debtPrice: number;
|
|
832
|
+
}): Promise<bigint[]> {
|
|
833
|
+
const collateralToken = this.config.collateral;
|
|
834
|
+
const debtToken = this.config.debt;
|
|
835
|
+
const { contract: multiplyContract } = this._getMultiplyContract();
|
|
836
|
+
this.lastSwapPriceInfo = null;
|
|
837
|
+
const ekuboQuoter = new EkuboQuoter(
|
|
838
|
+
this.config.networkConfig,
|
|
839
|
+
this.config.pricer
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
let leverSwap: Swap[] = [];
|
|
843
|
+
let leverSwapWeights: Web3Number[] = [];
|
|
844
|
+
let leverSwapLimitAmount = Web3Number.fromWei(0, collateralToken.decimals);
|
|
845
|
+
let leverCollateralUsed = Web3Number.fromWei(0, collateralToken.decimals);
|
|
846
|
+
let aggregatedFromAmount = 0; // collateral sold
|
|
847
|
+
let aggregatedToAmount = 0; // debt bought
|
|
848
|
+
let executedSwapCount = 0;
|
|
849
|
+
|
|
850
|
+
if (params.closePosition) {
|
|
851
|
+
const debtQuote = await ekuboQuoter.getQuoteExactOutput(
|
|
852
|
+
collateralToken.address.address,
|
|
853
|
+
debtToken.address.address,
|
|
854
|
+
params.debtToRepayAbs
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
const built = this._buildZeroAmountSwapsWithWeights(
|
|
858
|
+
debtQuote,
|
|
859
|
+
debtToken,
|
|
860
|
+
true
|
|
861
|
+
);
|
|
862
|
+
leverSwap = built.swaps;
|
|
863
|
+
leverSwapWeights = built.weights;
|
|
864
|
+
leverCollateralUsed = Web3Number.fromWei(
|
|
865
|
+
debtQuote.total_calculated,
|
|
866
|
+
collateralToken.decimals
|
|
867
|
+
).abs();
|
|
868
|
+
leverSwapLimitAmount = leverCollateralUsed.multipliedBy(1 + this.maxSlippage);
|
|
869
|
+
aggregatedFromAmount += leverCollateralUsed.toNumber();
|
|
870
|
+
aggregatedToAmount += params.debtToRepayAbs.abs().toNumber();
|
|
871
|
+
executedSwapCount += 1;
|
|
872
|
+
|
|
873
|
+
} else {
|
|
874
|
+
if (params.collateralPrice === undefined || params.debtPrice === undefined) {
|
|
875
|
+
throw new Error(
|
|
876
|
+
"VesuMultiplyAdapter: Missing prices for non-close decrease calldata"
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
const collateralToSwap = new Web3Number(
|
|
880
|
+
params.debtToRepayAbs
|
|
881
|
+
.multipliedBy(params.debtPrice)
|
|
882
|
+
.dividedBy(params.collateralPrice)
|
|
883
|
+
.toFixed(collateralToken.decimals),
|
|
884
|
+
collateralToken.decimals
|
|
885
|
+
);
|
|
886
|
+
const leverSwapQuote = await ekuboQuoter.getQuoteExactInput(
|
|
887
|
+
collateralToken.address.address,
|
|
888
|
+
debtToken.address.address,
|
|
889
|
+
collateralToSwap
|
|
890
|
+
);
|
|
891
|
+
if (leverSwapQuote.price_impact < 0.0025) {
|
|
892
|
+
const inputAmt = collateralToSwap.toNumber();
|
|
893
|
+
const outputAmt = Web3Number.fromWei(
|
|
894
|
+
leverSwapQuote.total_calculated,
|
|
895
|
+
debtToken.decimals
|
|
896
|
+
)
|
|
897
|
+
.abs()
|
|
898
|
+
.toNumber();
|
|
899
|
+
aggregatedFromAmount += inputAmt;
|
|
900
|
+
aggregatedToAmount += outputAmt;
|
|
901
|
+
executedSwapCount += 1;
|
|
902
|
+
|
|
903
|
+
leverSwap = ekuboQuoter.getVesuMultiplyQuote(
|
|
904
|
+
leverSwapQuote,
|
|
905
|
+
collateralToken,
|
|
906
|
+
debtToken
|
|
907
|
+
);
|
|
908
|
+
leverSwapLimitAmount = collateralToSwap
|
|
909
|
+
.multipliedBy(params.collateralPrice)
|
|
910
|
+
.dividedBy(params.debtPrice)
|
|
911
|
+
.multipliedBy(1 - this.maxSlippage);
|
|
912
|
+
leverSwapLimitAmount.decimals = debtToken.decimals;
|
|
913
|
+
} else {
|
|
914
|
+
throw new Error(`VesuMultiplyAdapter: Lever swap price impact too high (${leverSwapQuote.price_impact})`);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
leverCollateralUsed = Web3Number.fromWei(
|
|
918
|
+
leverSwapQuote.total_calculated,
|
|
919
|
+
collateralToken.decimals
|
|
920
|
+
).abs();
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
let withdrawSwap: Swap[] = [];
|
|
924
|
+
let withdrawSwapLimitAmount = Web3Number.fromWei(
|
|
925
|
+
0,
|
|
926
|
+
params.outputToken?.decimals ?? collateralToken.decimals
|
|
927
|
+
);
|
|
928
|
+
const withdrawSwapWeights: Web3Number[] = [];
|
|
929
|
+
|
|
930
|
+
if (params.outputToken && !params.outputToken.address.eq(collateralToken.address)) {
|
|
931
|
+
// bcz, last swap price is configured to be common between withdraw swap and lever swap,
|
|
932
|
+
// hence need same tokens in both
|
|
933
|
+
assert(params.outputToken.address.eq(debtToken.address), 'Withdraw output token must be the same as debt token');
|
|
934
|
+
const residualCollateral = params.closePosition
|
|
935
|
+
? params.existingCollateral.minus(leverCollateralUsed)
|
|
936
|
+
: params.subMargin;
|
|
937
|
+
const outputTokenPrice = await this.config.pricer.getPrice(params.outputToken.symbol);
|
|
938
|
+
if (residualCollateral.greaterThan(0)) {
|
|
939
|
+
await new Promise((r) => setTimeout(r, 10000));
|
|
940
|
+
const withdrawQuote = await ekuboQuoter.getQuoteExactInput(
|
|
941
|
+
collateralToken.address.address,
|
|
942
|
+
params.outputToken.address.address,
|
|
943
|
+
residualCollateral
|
|
944
|
+
);
|
|
945
|
+
if (withdrawQuote.price_impact < 0.0025) {
|
|
946
|
+
const built = this._buildZeroAmountSwapsWithWeights(
|
|
947
|
+
withdrawQuote,
|
|
948
|
+
collateralToken
|
|
949
|
+
);
|
|
950
|
+
withdrawSwap = built.swaps;
|
|
951
|
+
withdrawSwapWeights.push(...built.weights);
|
|
952
|
+
const withdrawOutputAmount = Web3Number.fromWei(
|
|
953
|
+
withdrawQuote.total_calculated,
|
|
954
|
+
params.outputToken.decimals
|
|
955
|
+
).abs().toNumber();
|
|
956
|
+
aggregatedFromAmount += residualCollateral.toNumber();
|
|
957
|
+
aggregatedToAmount += withdrawOutputAmount;
|
|
958
|
+
executedSwapCount += 1;
|
|
959
|
+
const estimatedOutput = residualCollateral
|
|
960
|
+
.multipliedBy(params.collateralPrice)
|
|
961
|
+
.dividedBy(outputTokenPrice.price);
|
|
962
|
+
estimatedOutput.decimals = params.outputToken.decimals;
|
|
963
|
+
withdrawSwapLimitAmount = estimatedOutput
|
|
964
|
+
.multipliedBy(1 - this.maxSlippage);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (executedSwapCount > 0) {
|
|
970
|
+
this.lastSwapPriceInfo = {
|
|
971
|
+
source: "ekubo",
|
|
972
|
+
fromTokenSymbol: collateralToken.symbol,
|
|
973
|
+
toTokenSymbol: debtToken.symbol,
|
|
974
|
+
fromAmount: aggregatedFromAmount,
|
|
975
|
+
toAmount: aggregatedToAmount,
|
|
976
|
+
effectivePrice: aggregatedFromAmount !== 0 ? aggregatedToAmount / aggregatedFromAmount : 0,
|
|
977
|
+
};
|
|
978
|
+
logger.verbose(
|
|
979
|
+
`${VesuMultiplyAdapter.name}::_buildDecreaseLikeCalldata stored aggregated price info: ` +
|
|
980
|
+
`${aggregatedFromAmount} ${collateralToken.symbol} → ${aggregatedToAmount} ${debtToken.symbol}, ` +
|
|
981
|
+
`effectivePrice=${this.lastSwapPriceInfo.effectivePrice}, swaps=${executedSwapCount}`
|
|
982
|
+
);
|
|
983
|
+
} else {
|
|
984
|
+
this.lastSwapPriceInfo = null;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
logger.debug(
|
|
988
|
+
`${VesuMultiplyAdapter.name}::_buildDecreaseLikeCalldata leverSwapCount=${leverSwap.length}, withdrawSwapCount=${withdrawSwap.length}, withdrawSwapLimitAmount=${withdrawSwapLimitAmount.toNumber()}, subMargin=${params.subMargin.toNumber()}`,
|
|
989
|
+
);
|
|
990
|
+
const multiplyParams: DecreaseLeverParams = {
|
|
711
991
|
user: this.config.vaultAllocator,
|
|
712
992
|
pool_id: this.config.poolId,
|
|
713
|
-
collateral_asset:
|
|
714
|
-
debt_asset:
|
|
993
|
+
collateral_asset: collateralToken.address,
|
|
994
|
+
debt_asset: debtToken.address,
|
|
715
995
|
recipient: this.config.vaultAllocator,
|
|
716
|
-
sub_margin: params.
|
|
996
|
+
sub_margin: params.closePosition
|
|
997
|
+
? Web3Number.fromWei(0, collateralToken.decimals)
|
|
998
|
+
: params.subMargin,
|
|
717
999
|
lever_swap: leverSwap,
|
|
718
1000
|
lever_swap_limit_amount: leverSwapLimitAmount,
|
|
719
|
-
lever_swap_weights:
|
|
720
|
-
withdraw_swap:
|
|
721
|
-
withdraw_swap_limit_amount:
|
|
722
|
-
withdraw_swap_weights:
|
|
723
|
-
close_position:
|
|
1001
|
+
lever_swap_weights: leverSwapWeights,
|
|
1002
|
+
withdraw_swap: withdrawSwap,
|
|
1003
|
+
withdraw_swap_limit_amount: withdrawSwapLimitAmount,
|
|
1004
|
+
withdraw_swap_weights: withdrawSwapWeights,
|
|
1005
|
+
close_position: params.closePosition,
|
|
724
1006
|
};
|
|
725
|
-
return multiplyParams;
|
|
726
|
-
}
|
|
727
1007
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const { isV2 } = getVesuSingletonAddress(this.config.poolId);
|
|
731
|
-
const vesuMultiply = isV2 ? this.vesuAdapter.VESU_MULTIPLY : this.vesuAdapter.VESU_MULTIPLY_V1;
|
|
732
|
-
const multiplyContract = new Contract({
|
|
733
|
-
abi: VesuMultiplyAbi,
|
|
734
|
-
address: vesuMultiply.address,
|
|
735
|
-
providerOrAccount: this.config.networkConfig.provider
|
|
1008
|
+
const call = multiplyContract.populate("modify_lever", {
|
|
1009
|
+
modify_lever_params: this.formatMultiplyParams(false, multiplyParams),
|
|
736
1010
|
});
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1011
|
+
return call.calldata as bigint[];
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private async _getDecreaseCalldata(
|
|
1015
|
+
params: VesuWithdrawParams
|
|
1016
|
+
): Promise<bigint[]> {
|
|
742
1017
|
const collateralToken = this.config.collateral;
|
|
743
1018
|
const debtToken = this.config.debt;
|
|
744
|
-
|
|
1019
|
+
|
|
1020
|
+
this._vesuAdapter.networkConfig = this.config.networkConfig;
|
|
1021
|
+
this._vesuAdapter.pricer = this.config.pricer;
|
|
1022
|
+
const existingPositions = await this._vesuAdapter.getPositions(
|
|
1023
|
+
this.config.networkConfig
|
|
1024
|
+
);
|
|
1025
|
+
const existingCollateralInfo = existingPositions[0];
|
|
1026
|
+
const existingDebtInfo = existingPositions[1];
|
|
1027
|
+
const collateralPrice = await this.config.pricer.getPrice(
|
|
1028
|
+
collateralToken.symbol
|
|
1029
|
+
);
|
|
745
1030
|
const debtPrice = await this.config.pricer.getPrice(debtToken.symbol);
|
|
746
|
-
|
|
1031
|
+
|
|
747
1032
|
const { deltadebtAmountUnits: debtAmountToRepay } =
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if(!debtAmountToRepay) {
|
|
1033
|
+
calculateDebtReductionAmountForWithdrawal(
|
|
1034
|
+
existingDebtInfo.amount,
|
|
1035
|
+
existingCollateralInfo.amount,
|
|
1036
|
+
MAX_LIQUIDATION_RATIO,
|
|
1037
|
+
params.amount,
|
|
1038
|
+
collateralPrice.price,
|
|
1039
|
+
debtPrice.price,
|
|
1040
|
+
debtToken.decimals
|
|
1041
|
+
);
|
|
1042
|
+
if (!debtAmountToRepay) {
|
|
759
1043
|
throw new Error("error calculating debt amount to repay");
|
|
760
1044
|
}
|
|
761
|
-
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
debtToken.
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
1045
|
+
|
|
1046
|
+
const debtToRepayAbs = new Web3Number(
|
|
1047
|
+
debtAmountToRepay,
|
|
1048
|
+
debtToken.decimals
|
|
1049
|
+
).abs();
|
|
1050
|
+
const existingDebtAbs = existingDebtInfo.amount.abs();
|
|
1051
|
+
|
|
1052
|
+
const shouldCloseByDebt = debtToRepayAbs.greaterThanOrEqualTo(existingDebtAbs);
|
|
1053
|
+
const remainingDebtUSD = existingDebtAbs
|
|
1054
|
+
.minus(debtToRepayAbs)
|
|
1055
|
+
.multipliedBy(debtPrice.price)
|
|
1056
|
+
.toNumber();
|
|
1057
|
+
const shouldCloseByDust = remainingDebtUSD < MIN_REMAINING_DEBT_USD;
|
|
1058
|
+
|
|
1059
|
+
if (shouldCloseByDebt) {
|
|
1060
|
+
logger.info(
|
|
1061
|
+
`${VesuMultiplyAdapter.name}::_getDecreaseCalldata debt to repay (${debtToRepayAbs.toNumber()}) >= existing debt (${existingDebtAbs.toNumber()}), auto-closing position`
|
|
1062
|
+
);
|
|
1063
|
+
} else if (shouldCloseByDust) {
|
|
1064
|
+
logger.info(
|
|
1065
|
+
`${VesuMultiplyAdapter.name}::_getDecreaseCalldata remaining debt $${remainingDebtUSD.toFixed(2)} < $${MIN_REMAINING_DEBT_USD}, auto-closing position`
|
|
1066
|
+
);
|
|
773
1067
|
}
|
|
774
1068
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1069
|
+
return this._buildDecreaseLikeCalldata({
|
|
1070
|
+
subMargin: params.amount,
|
|
1071
|
+
debtToRepayAbs: shouldCloseByDebt || shouldCloseByDust ? existingDebtAbs : debtToRepayAbs,
|
|
1072
|
+
existingCollateral: existingCollateralInfo.amount,
|
|
1073
|
+
closePosition: shouldCloseByDebt || shouldCloseByDust,
|
|
1074
|
+
outputToken: params.withdrawSwap?.outputToken,
|
|
1075
|
+
collateralPrice: collateralPrice.price,
|
|
1076
|
+
debtPrice: debtPrice.price,
|
|
780
1077
|
});
|
|
781
|
-
return call.calldata as bigint[];
|
|
782
1078
|
}
|
|
783
1079
|
|
|
784
|
-
|
|
1080
|
+
// private async _getClosePositionCalldata(
|
|
1081
|
+
// outputToken?: TokenInfo
|
|
1082
|
+
// ): Promise<bigint[]> {
|
|
1083
|
+
// this._vesuAdapter.networkConfig = this.config.networkConfig;
|
|
1084
|
+
// this._vesuAdapter.pricer = this.config.pricer;
|
|
1085
|
+
// const existingPositions = await this._vesuAdapter.getPositions(
|
|
1086
|
+
// this.config.networkConfig
|
|
1087
|
+
// );
|
|
1088
|
+
// const existingCollateralInfo = existingPositions[0];
|
|
1089
|
+
// const existingDebtInfo = existingPositions[1];
|
|
1090
|
+
|
|
1091
|
+
// if (existingDebtInfo.amount.isZero()) {
|
|
1092
|
+
// throw new Error("VesuMultiplyAdapter: No debt to close");
|
|
1093
|
+
// }
|
|
1094
|
+
|
|
1095
|
+
// return this._buildDecreaseLikeCalldata({
|
|
1096
|
+
// subMargin: Web3Number.fromWei(0, this.config.collateral.decimals),
|
|
1097
|
+
// debtToRepayAbs: existingDebtInfo.amount.abs(),
|
|
1098
|
+
// existingCollateral: existingCollateralInfo.amount,
|
|
1099
|
+
// closePosition: true,
|
|
1100
|
+
// outputToken,
|
|
1101
|
+
// });
|
|
1102
|
+
// }
|
|
1103
|
+
|
|
1104
|
+
// ─── Param Formatting ─────────────────────────────────────────────────────
|
|
1105
|
+
|
|
1106
|
+
formatMultiplyParams(
|
|
1107
|
+
isIncrease: boolean,
|
|
1108
|
+
params: IncreaseLeverParams | DecreaseLeverParams
|
|
1109
|
+
) {
|
|
785
1110
|
if (isIncrease) {
|
|
786
1111
|
const _params = params as IncreaseLeverParams;
|
|
787
1112
|
return {
|
|
788
|
-
action: new CairoCustomEnum({
|
|
1113
|
+
action: new CairoCustomEnum({
|
|
1114
|
+
IncreaseLever: {
|
|
1115
|
+
pool_id: _params.pool_id.toBigInt(),
|
|
1116
|
+
collateral_asset: _params.collateral_asset.toBigInt(),
|
|
1117
|
+
debt_asset: _params.debt_asset.toBigInt(),
|
|
1118
|
+
user: _params.user.toBigInt(),
|
|
1119
|
+
add_margin: BigInt(_params.add_margin.toWei()),
|
|
1120
|
+
margin_swap: _params.margin_swap.map((swap) => ({
|
|
1121
|
+
route: swap.route.map((route) => ({
|
|
1122
|
+
pool_key: {
|
|
1123
|
+
token0: route.pool_key.token0.toBigInt(),
|
|
1124
|
+
token1: route.pool_key.token1.toBigInt(),
|
|
1125
|
+
fee: route.pool_key.fee,
|
|
1126
|
+
tick_spacing: route.pool_key.tick_spacing,
|
|
1127
|
+
extension: BigInt(
|
|
1128
|
+
num.hexToDecimalString(route.pool_key.extension)
|
|
1129
|
+
),
|
|
1130
|
+
},
|
|
1131
|
+
sqrt_ratio_limit: uint256.bnToUint256(
|
|
1132
|
+
route.sqrt_ratio_limit.toWei()
|
|
1133
|
+
),
|
|
1134
|
+
skip_ahead: BigInt(100),
|
|
1135
|
+
})),
|
|
1136
|
+
token_amount: {
|
|
1137
|
+
token: swap.token_amount.token.toBigInt(),
|
|
1138
|
+
amount: swap.token_amount.amount.toI129(),
|
|
1139
|
+
},
|
|
1140
|
+
})),
|
|
1141
|
+
margin_swap_limit_amount: BigInt(
|
|
1142
|
+
_params.margin_swap_limit_amount.toWei()
|
|
1143
|
+
),
|
|
1144
|
+
lever_swap: _params.lever_swap.map((swap) => ({
|
|
1145
|
+
route: swap.route.map((route) => ({
|
|
1146
|
+
pool_key: {
|
|
1147
|
+
token0: route.pool_key.token0.toBigInt(),
|
|
1148
|
+
token1: route.pool_key.token1.toBigInt(),
|
|
1149
|
+
fee: route.pool_key.fee,
|
|
1150
|
+
tick_spacing: route.pool_key.tick_spacing,
|
|
1151
|
+
extension: BigInt(
|
|
1152
|
+
num.hexToDecimalString(route.pool_key.extension)
|
|
1153
|
+
),
|
|
1154
|
+
},
|
|
1155
|
+
sqrt_ratio_limit: uint256.bnToUint256(
|
|
1156
|
+
route.sqrt_ratio_limit.toWei()
|
|
1157
|
+
),
|
|
1158
|
+
skip_ahead: BigInt(0),
|
|
1159
|
+
})),
|
|
1160
|
+
token_amount: {
|
|
1161
|
+
token: swap.token_amount.token.toBigInt(),
|
|
1162
|
+
amount: swap.token_amount.amount.toI129(),
|
|
1163
|
+
},
|
|
1164
|
+
})),
|
|
1165
|
+
lever_swap_limit_amount: BigInt(
|
|
1166
|
+
_params.lever_swap_limit_amount.toWei()
|
|
1167
|
+
),
|
|
1168
|
+
},
|
|
1169
|
+
}),
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const _params = params as DecreaseLeverParams;
|
|
1174
|
+
return {
|
|
1175
|
+
action: new CairoCustomEnum({
|
|
1176
|
+
DecreaseLever: {
|
|
789
1177
|
pool_id: _params.pool_id.toBigInt(),
|
|
790
1178
|
collateral_asset: _params.collateral_asset.toBigInt(),
|
|
791
1179
|
debt_asset: _params.debt_asset.toBigInt(),
|
|
792
1180
|
user: _params.user.toBigInt(),
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1181
|
+
sub_margin: BigInt(_params.sub_margin.toWei()),
|
|
1182
|
+
recipient: _params.recipient.toBigInt(),
|
|
1183
|
+
lever_swap: _params.lever_swap.map((swap) => ({
|
|
1184
|
+
route: swap.route.map((route) => ({
|
|
796
1185
|
pool_key: {
|
|
797
1186
|
token0: route.pool_key.token0.toBigInt(),
|
|
798
1187
|
token1: route.pool_key.token1.toBigInt(),
|
|
799
1188
|
fee: route.pool_key.fee,
|
|
800
1189
|
tick_spacing: route.pool_key.tick_spacing,
|
|
801
|
-
extension:
|
|
1190
|
+
extension: ContractAddr.from(
|
|
1191
|
+
route.pool_key.extension
|
|
1192
|
+
).toBigInt(),
|
|
802
1193
|
},
|
|
803
|
-
sqrt_ratio_limit: uint256.bnToUint256(
|
|
804
|
-
|
|
1194
|
+
sqrt_ratio_limit: uint256.bnToUint256(
|
|
1195
|
+
route.sqrt_ratio_limit.toWei()
|
|
1196
|
+
),
|
|
1197
|
+
skip_ahead: BigInt(route.skip_ahead.toWei()),
|
|
805
1198
|
})),
|
|
806
1199
|
token_amount: {
|
|
807
1200
|
token: swap.token_amount.token.toBigInt(),
|
|
808
|
-
amount: swap.token_amount.amount.toI129()
|
|
809
|
-
}
|
|
1201
|
+
amount: swap.token_amount.amount.toI129(),
|
|
1202
|
+
},
|
|
810
1203
|
})),
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1204
|
+
lever_swap_limit_amount: BigInt(
|
|
1205
|
+
_params.lever_swap_limit_amount.toWei()
|
|
1206
|
+
),
|
|
1207
|
+
lever_swap_weights: _params.lever_swap_weights.map((weight) =>
|
|
1208
|
+
BigInt(weight.toWei())
|
|
1209
|
+
),
|
|
1210
|
+
withdraw_swap: _params.withdraw_swap.map((swap) => ({
|
|
1211
|
+
route: swap.route.map((route) => ({
|
|
814
1212
|
pool_key: {
|
|
815
1213
|
token0: route.pool_key.token0.toBigInt(),
|
|
816
1214
|
token1: route.pool_key.token1.toBigInt(),
|
|
817
1215
|
fee: route.pool_key.fee,
|
|
818
1216
|
tick_spacing: route.pool_key.tick_spacing,
|
|
819
|
-
extension:
|
|
1217
|
+
extension: ContractAddr.from(
|
|
1218
|
+
route.pool_key.extension
|
|
1219
|
+
).toBigInt(),
|
|
820
1220
|
},
|
|
821
|
-
sqrt_ratio_limit: uint256.bnToUint256(
|
|
822
|
-
|
|
1221
|
+
sqrt_ratio_limit: uint256.bnToUint256(
|
|
1222
|
+
route.sqrt_ratio_limit.toWei()
|
|
1223
|
+
),
|
|
1224
|
+
skip_ahead: BigInt(route.skip_ahead.toWei()),
|
|
823
1225
|
})),
|
|
824
1226
|
token_amount: {
|
|
825
1227
|
token: swap.token_amount.token.toBigInt(),
|
|
826
|
-
amount: swap.token_amount.amount.toI129()
|
|
827
|
-
}
|
|
828
|
-
})),
|
|
829
|
-
lever_swap_limit_amount: BigInt(_params.lever_swap_limit_amount.toWei()),
|
|
830
|
-
} }),
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
const _params = params as DecreaseLeverParams;
|
|
835
|
-
return {
|
|
836
|
-
action: new CairoCustomEnum({ DecreaseLever: {
|
|
837
|
-
pool_id: _params.pool_id.toBigInt(),
|
|
838
|
-
collateral_asset: _params.collateral_asset.toBigInt(),
|
|
839
|
-
debt_asset: _params.debt_asset.toBigInt(),
|
|
840
|
-
user: _params.user.toBigInt(),
|
|
841
|
-
sub_margin: BigInt(_params.sub_margin.toWei()),
|
|
842
|
-
recipient: _params.recipient.toBigInt(),
|
|
843
|
-
lever_swap: _params.lever_swap.map(swap => ({
|
|
844
|
-
route: swap.route.map(route => ({
|
|
845
|
-
pool_key: {
|
|
846
|
-
token0: route.pool_key.token0.toBigInt(),
|
|
847
|
-
token1: route.pool_key.token1.toBigInt(),
|
|
848
|
-
fee: route.pool_key.fee,
|
|
849
|
-
tick_spacing: route.pool_key.tick_spacing,
|
|
850
|
-
extension: ContractAddr.from(route.pool_key.extension).toBigInt(),
|
|
1228
|
+
amount: swap.token_amount.amount.toI129(),
|
|
851
1229
|
},
|
|
852
|
-
sqrt_ratio_limit: uint256.bnToUint256(route.sqrt_ratio_limit.toWei()),
|
|
853
|
-
skip_ahead: BigInt(route.skip_ahead.toWei())
|
|
854
1230
|
})),
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
token0: route.pool_key.token0.toBigInt(),
|
|
866
|
-
token1: route.pool_key.token1.toBigInt(),
|
|
867
|
-
fee: route.pool_key.fee,
|
|
868
|
-
tick_spacing: route.pool_key.tick_spacing,
|
|
869
|
-
extension: ContractAddr.from(route.pool_key.extension).toBigInt(),
|
|
870
|
-
},
|
|
871
|
-
sqrt_ratio_limit: uint256.bnToUint256(route.sqrt_ratio_limit.toWei()),
|
|
872
|
-
skip_ahead: BigInt(route.skip_ahead.toWei())
|
|
873
|
-
})),
|
|
874
|
-
token_amount: {
|
|
875
|
-
token: swap.token_amount.token.toBigInt(),
|
|
876
|
-
amount: swap.token_amount.amount.toI129()
|
|
877
|
-
}
|
|
878
|
-
})),
|
|
879
|
-
withdraw_swap_limit_amount: BigInt(_params.withdraw_swap_limit_amount.toWei()),
|
|
880
|
-
withdraw_swap_weights: _params.withdraw_swap_weights.map(weight => BigInt(weight.toWei())),
|
|
881
|
-
close_position: _params.close_position,
|
|
882
|
-
}}),
|
|
883
|
-
}
|
|
1231
|
+
withdraw_swap_limit_amount: BigInt(
|
|
1232
|
+
_params.withdraw_swap_limit_amount.toWei()
|
|
1233
|
+
),
|
|
1234
|
+
withdraw_swap_weights: _params.withdraw_swap_weights.map((weight) =>
|
|
1235
|
+
BigInt(weight.toWei())
|
|
1236
|
+
),
|
|
1237
|
+
close_position: _params.close_position,
|
|
1238
|
+
},
|
|
1239
|
+
}),
|
|
1240
|
+
};
|
|
884
1241
|
}
|
|
885
1242
|
|
|
1243
|
+
// ─── Health Factor / Net APY ───────────────────────────────────────────────
|
|
1244
|
+
|
|
886
1245
|
async getHealthFactor(): Promise<number> {
|
|
887
|
-
const healthFactor = await this.
|
|
1246
|
+
const healthFactor = await this._vesuAdapter.getHealthFactor();
|
|
888
1247
|
return healthFactor;
|
|
889
1248
|
}
|
|
890
1249
|
|
|
891
1250
|
async getNetAPY(): Promise<number> {
|
|
892
1251
|
const positions = await this.getPositions();
|
|
893
|
-
logger.verbose(
|
|
894
|
-
|
|
1252
|
+
logger.verbose(
|
|
1253
|
+
`${this.name}::getNetAPY: positions: ${JSON.stringify(positions)}`
|
|
1254
|
+
);
|
|
1255
|
+
const allZero = positions.every((p) => p.usdValue === 0);
|
|
895
1256
|
|
|
896
|
-
// in case of zero positions, apy will come zero/NaN
|
|
897
|
-
// bcz of net 0 zero weights
|
|
898
1257
|
if (allZero) {
|
|
899
|
-
// use approx dummy usd values to compute netAPY
|
|
900
1258
|
const collateralUSD = 1000;
|
|
901
|
-
const maxLTV = await this.
|
|
1259
|
+
const maxLTV = await this._vesuAdapter.getLTVConfig(
|
|
1260
|
+
this.config.networkConfig
|
|
1261
|
+
);
|
|
902
1262
|
const targetHF = this.config.targetHealthFactor;
|
|
903
1263
|
const maxDebt = HealthFactorMath.getMaxDebtAmountOnLooping(
|
|
904
1264
|
new Web3Number(collateralUSD, this.config.collateral.decimals),
|
|
905
|
-
1,
|
|
1265
|
+
1,
|
|
906
1266
|
maxLTV,
|
|
907
1267
|
targetHF,
|
|
908
|
-
1,
|
|
1268
|
+
1,
|
|
909
1269
|
this.config.debt
|
|
910
|
-
)
|
|
1270
|
+
);
|
|
911
1271
|
|
|
912
|
-
|
|
913
|
-
const
|
|
914
|
-
|
|
1272
|
+
const debtUSD = maxDebt.multipliedBy(1);
|
|
1273
|
+
const netAPY =
|
|
1274
|
+
(positions[0].apy.apy * (collateralUSD + debtUSD.toNumber()) +
|
|
1275
|
+
positions[1].apy.apy * debtUSD.toNumber()) /
|
|
1276
|
+
collateralUSD;
|
|
915
1277
|
return netAPY;
|
|
916
1278
|
}
|
|
917
1279
|
|
|
918
|
-
// Return true APY
|
|
919
1280
|
const netAmount = positions.reduce((acc, curr) => acc + curr.usdValue, 0);
|
|
920
|
-
const netAPY =
|
|
1281
|
+
const netAPY =
|
|
1282
|
+
positions.reduce((acc, curr) => acc + curr.apy.apy * curr.usdValue, 0) /
|
|
1283
|
+
netAmount;
|
|
921
1284
|
logger.verbose(`${this.name}::getNetAPY: netAPY: ${netAPY}`);
|
|
922
1285
|
return netAPY;
|
|
923
1286
|
}
|