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