@strkfarm/sdk 1.0.55 → 1.0.56

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.
@@ -0,0 +1,274 @@
1
+ import { LeafData, logger } from "@/utils"
2
+ import { CairoCustomEnum, Contract, hash, num, RpcProvider, shortString, uint256, Uint256 } from "starknet";
3
+ import { SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
4
+ import { ContractAddr, Web3Number } from "@/dataTypes";
5
+ import { AdapterLeafType, BaseAdapter, GenerateCallFn, ManageCall } from "./baseAdapter";
6
+ import VesuSingletonAbi from '../../data/vesu-singleton.abi.json';
7
+ import { IConfig, TokenInfo, VaultPosition } from "@/interfaces";
8
+ import { PricerBase } from "@/modules/pricerBase";
9
+ import VesuPoolIDs from "@/data/vesu_pools.json";
10
+ import { getAPIUsingHeadlessBrowser } from "@/node/headless";
11
+ import { Global } from "@/global";
12
+
13
+ interface VesuPoolsInfo { pools: any[]; isErrorPoolsAPI: boolean };
14
+
15
+ export enum VesuAmountType {
16
+ Delta,
17
+ Target
18
+ }
19
+
20
+ export enum VesuAmountDenomination {
21
+ Native,
22
+ Assets
23
+ }
24
+
25
+ export interface i257 {
26
+ abs: Web3Number,
27
+ is_negative: boolean,
28
+ }
29
+
30
+ export interface VesuAmount {
31
+ amount_type: VesuAmountType,
32
+ denomination: VesuAmountDenomination,
33
+ value: i257
34
+ }
35
+
36
+ export interface VesuModifyPositionCallParams {
37
+ collateralAmount: VesuAmount,
38
+ debtAmount: VesuAmount
39
+ }
40
+
41
+ export interface VesuAdapterConfig {
42
+ poolId: ContractAddr,
43
+ collateral: TokenInfo,
44
+ debt: TokenInfo,
45
+ vaultAllocator: ContractAddr,
46
+ id: string
47
+ }
48
+
49
+ export const VesuPools = {
50
+ Genesis: ContractAddr.from('0x4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28')
51
+ }
52
+
53
+ export class VesuAdapter extends BaseAdapter {
54
+ VESU_SINGLETON = ContractAddr.from("0x000d8d6dfec4d33bfb6895de9f3852143a17c6f92fd2a21da3d6924d34870160");
55
+ config: VesuAdapterConfig;
56
+ networkConfig: IConfig | undefined;
57
+ pricer: PricerBase | undefined;
58
+ cache: Record<string, any> = {};
59
+
60
+ constructor(config: VesuAdapterConfig) {
61
+ super();
62
+ this.config = config;
63
+ }
64
+
65
+ getModifyPosition = (): AdapterLeafType<VesuModifyPositionCallParams> => {
66
+ const positionData: bigint[] = [0n];
67
+ const packedArguments: bigint[] = [
68
+ toBigInt(this.config.poolId.toString()), // pool id
69
+ toBigInt(this.config.collateral.address.toString()), // collateral
70
+ toBigInt(this.config.debt.address.toString()), // debt
71
+ toBigInt(this.config.vaultAllocator.toString()), // vault allocator
72
+ toBigInt(positionData.length),
73
+ ...positionData
74
+ ];
75
+ const output = this.constructSimpleLeafData({
76
+ id: this.config.id,
77
+ target: this.VESU_SINGLETON,
78
+ method: 'modify_position',
79
+ packedArguments
80
+ });
81
+
82
+ return { leaf: output, callConstructor: this.getModifyPositionCall.bind(this) };
83
+ }
84
+
85
+ static getDefaultModifyPositionCallParams(params: {
86
+ collateralAmount: Web3Number,
87
+ isAddCollateral: boolean,
88
+ debtAmount: Web3Number,
89
+ isBorrow: boolean
90
+ }) {
91
+ return {
92
+ collateralAmount: {
93
+ amount_type: VesuAmountType.Delta,
94
+ denomination: VesuAmountDenomination.Assets,
95
+ value: {
96
+ abs: params.collateralAmount,
97
+ is_negative: !params.isAddCollateral
98
+ }
99
+ },
100
+ debtAmount: {
101
+ amount_type: VesuAmountType.Delta,
102
+ denomination: VesuAmountDenomination.Assets,
103
+ value: {
104
+ abs: params.debtAmount,
105
+ is_negative: !params.isBorrow
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ getModifyPositionCall = (params: VesuModifyPositionCallParams): ManageCall => {
112
+ // pub pool_id: felt252,
113
+ // pub collateral_asset: ContractAddress,
114
+ // pub debt_asset: ContractAddress,
115
+ // pub user: ContractAddress,
116
+ // pub collateral: Amount,
117
+ // pub debt: Amount,
118
+ // pub data: Span<felt252>,
119
+ const _collateral = {
120
+ amount_type: this.formatAmountTypeEnum(params.collateralAmount.amount_type),
121
+ denomination: this.formatAmountDenominationEnum(params.collateralAmount.denomination),
122
+ value: {
123
+ abs: uint256.bnToUint256(params.collateralAmount.value.abs.toWei()),
124
+ is_negative: params.collateralAmount.value.is_negative
125
+ }
126
+ };
127
+ const _debt = {
128
+ amount_type: this.formatAmountTypeEnum(params.debtAmount.amount_type),
129
+ denomination: this.formatAmountDenominationEnum(params.debtAmount.denomination),
130
+ value: {
131
+ abs: uint256.bnToUint256(params.debtAmount.value.abs.toWei()),
132
+ is_negative: params.debtAmount.value.is_negative
133
+ }
134
+ };
135
+ const singletonContract = new Contract(VesuSingletonAbi, this.VESU_SINGLETON.toString(), new RpcProvider({nodeUrl: ''}));
136
+ const call = singletonContract.populate('modify_position', {
137
+ params: {
138
+ pool_id: this.config.poolId.toBigInt(),
139
+ collateral_asset: this.config.collateral.address.toBigInt(),
140
+ debt_asset: this.config.debt.address.toBigInt(),
141
+ user: this.config.vaultAllocator.toBigInt(),
142
+ collateral: _collateral,
143
+ debt: _debt,
144
+ data: [0]
145
+ }
146
+ });
147
+ return {
148
+ sanitizer: SIMPLE_SANITIZER,
149
+ call: {
150
+ contractAddress: this.VESU_SINGLETON,
151
+ selector: hash.getSelectorFromName('modify_position'),
152
+ calldata: [
153
+ ...call.calldata as bigint[]
154
+ ]
155
+ }
156
+ }
157
+ }
158
+
159
+ formatAmountTypeEnum(amountType: VesuAmountType) {
160
+ switch(amountType) {
161
+ case VesuAmountType.Delta:
162
+ return new CairoCustomEnum({ Delta: {} });
163
+ case VesuAmountType.Target:
164
+ return new CairoCustomEnum({ Target: {} });
165
+ }
166
+ throw new Error(`Unknown VesuAmountType: ${amountType}`);
167
+ }
168
+
169
+ formatAmountDenominationEnum(denomination: VesuAmountDenomination) {
170
+ switch(denomination) {
171
+ case VesuAmountDenomination.Native:
172
+ return new CairoCustomEnum({ Native: {} });
173
+ case VesuAmountDenomination.Assets:
174
+ return new CairoCustomEnum({ Assets: {} });
175
+ }
176
+ throw new Error(`Unknown VesuAmountDenomination: ${denomination}`);
177
+ }
178
+
179
+ getVesuSingletonContract(config: IConfig) {
180
+ return new Contract(VesuSingletonAbi, this.VESU_SINGLETON.address, config.provider);
181
+ }
182
+
183
+ async getLTVConfig(config: IConfig) {
184
+ const CACHE_KEY = 'ltv_config';
185
+ if (this.cache[CACHE_KEY]) {
186
+ return this.cache[CACHE_KEY] as number;
187
+ }
188
+ const output: any = await this.getVesuSingletonContract(config)
189
+ .call('ltv_config', [this.config.poolId.address, this.config.collateral.address.address, this.config.debt.address.address])
190
+ this.cache[CACHE_KEY] = Number(output.max_ltv) / 1e18;
191
+ return this.cache[CACHE_KEY] as number;
192
+ }
193
+
194
+ async getPositions(config: IConfig): Promise<VaultPosition[]> {
195
+ if (!this.pricer) {
196
+ throw new Error('Pricer is not initialized');
197
+ }
198
+ // { '0': { collateral_shares: 0n, nominal_debt: 0n }, '1': 0n, '2': 0n }
199
+ const CACHE_KEY = 'positions';
200
+ if (this.cache[CACHE_KEY]) {
201
+ return this.cache[CACHE_KEY];
202
+ }
203
+ const output: any = await this.getVesuSingletonContract(config)
204
+ .call('position_unsafe', [
205
+ this.config.poolId.address,
206
+ this.config.collateral.address.address,
207
+ this.config.debt.address.address,
208
+ this.config.vaultAllocator.address
209
+ ]);
210
+
211
+ const token1Price = await this.pricer.getPrice(this.config.collateral.symbol);
212
+ const token2Price = await this.pricer.getPrice(this.config.debt.symbol);
213
+
214
+ const collateralAmount = Web3Number.fromWei(output['1'].toString(), this.config.collateral.decimals);
215
+ const debtAmount = Web3Number.fromWei(output['2'].toString(), this.config.debt.decimals);
216
+ const value = [{
217
+ amount: collateralAmount,
218
+ token: this.config.collateral,
219
+ usdValue: collateralAmount.multipliedBy(token1Price.price).toNumber(),
220
+ remarks: "Collateral"
221
+ }, {
222
+ amount: debtAmount,
223
+ token: this.config.debt,
224
+ usdValue: debtAmount.multipliedBy(token2Price.price).toNumber(),
225
+ remarks: "Debt"
226
+ }];
227
+ this.cache[CACHE_KEY] = value;
228
+ return value;
229
+ }
230
+
231
+ static async getVesuPools(
232
+ retry = 0
233
+ ): Promise<VesuPoolsInfo> {
234
+ const CACHE_KEY = 'VESU_POOLS';
235
+ const cacheValue = Global.getGlobalCache<VesuPoolsInfo>(CACHE_KEY);
236
+ if (cacheValue) {
237
+ return cacheValue;
238
+ }
239
+
240
+ let isErrorPoolsAPI = false;
241
+ let pools: any[] = [];
242
+ try {
243
+ const data = await getAPIUsingHeadlessBrowser(
244
+ "https://api.vesu.xyz/pools"
245
+ );
246
+ pools = data.data;
247
+
248
+ // Vesu API is unstable sometimes, some Pools may be missing sometimes
249
+ for (const pool of VesuPoolIDs.data) {
250
+ const found = pools.find((d: any) => d.id === pool.id);
251
+ if (!found) {
252
+ logger.verbose(`VesuRebalance: pools: ${JSON.stringify(pools)}`);
253
+ logger.verbose(
254
+ `VesuRebalance: Pool ${pool.id} not found in Vesu API, using hardcoded data`
255
+ );
256
+ throw new Error(`pool not found [sanity check]: ${pool.id}`);
257
+ }
258
+ }
259
+ } catch (e) {
260
+ logger.error(
261
+ `${VesuAdapter.name}: Error fetching pools for retry ${retry}`,
262
+ e
263
+ );
264
+ isErrorPoolsAPI = true;
265
+ if (retry < 10) {
266
+ await new Promise((resolve) => setTimeout(resolve, 5000 * (retry + 1)));
267
+ return await this.getVesuPools(retry + 1);
268
+ }
269
+ }
270
+
271
+ Global.setGlobalCache(CACHE_KEY, { pools, isErrorPoolsAPI }, 300000); // cache for 5 minutes
272
+ return { pools, isErrorPoolsAPI };
273
+ }
274
+ }