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