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