@strkfarm/sdk 1.1.38 → 1.1.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.
@@ -1,5 +1,5 @@
1
1
  import { LeafData, logger } from "@/utils"
2
- import { CairoCustomEnum, Contract, hash, num, RpcProvider, shortString, uint256, Uint256 } from "starknet";
2
+ import { BlockIdentifier, CairoCustomEnum, Contract, hash, num, RpcProvider, shortString, uint256, Uint256 } from "starknet";
3
3
  import { SIMPLE_SANITIZER, SIMPLE_SANITIZER_V2, SIMPLE_SANITIZER_VESU_V1_DELEGATIONS, toBigInt, VESU_SINGLETON, VESU_V2_MODIFY_POSITION_SANITIZER } from "./adapter-utils";
4
4
  import { ContractAddr, Web3Number } from "@/dataTypes";
5
5
  import { AdapterLeafType, BaseAdapter, GenerateCallFn, LeafAdapterFn, ManageCall } from "./baseAdapter";
@@ -536,7 +536,22 @@ export class VesuAdapter extends BaseAdapter {
536
536
  }
537
537
  const output: any = await contract.call('pair_config', [this.config.collateral.address.address, this.config.debt.address.address]);
538
538
  logger.verbose(`${this.config.debt.symbol}::VesuAdapter::getDebtCap debt_cap: ${output.debt_cap.toString()}`);
539
- return Web3Number.fromWei(output.debt_cap.toString(), this.config.debt.decimals);
539
+
540
+ if (!isV2) {
541
+ throw new Error('getDebtCap is not supported for v1');
542
+ }
543
+ const currentDebt = await this.getCurrentDebtUtilisationAmount(config);
544
+ logger.verbose(`${this.config.debt.symbol}::VesuAdapter::getDebtCap currentDebt: ${currentDebt.toString()}`);
545
+ return Web3Number.fromWei(output.debt_cap.toString(), this.config.debt.decimals).minus(currentDebt);
546
+ }
547
+
548
+ async getCurrentDebtUtilisationAmount(config: IConfig) {
549
+ const { contract, isV2 } = await this.getVesuSingletonContract(config, this.config.poolId);
550
+ if (!isV2) {
551
+ throw new Error('getCurrentDebtUtilisationAmount is not supported for v1');
552
+ }
553
+ const output: any = await contract.call('pairs', [this.config.collateral.address.address, this.config.debt.address.address]);
554
+ return new Web3Number((Number(output.total_nominal_debt) / 1e18).toFixed(9), this.config.debt.decimals);
540
555
  }
541
556
 
542
557
  async getMaxBorrowableByInterestRate(config: IConfig, asset: TokenInfo, maxBorrowAPY: number) {
@@ -572,19 +587,20 @@ export class VesuAdapter extends BaseAdapter {
572
587
  const assetConfig = isV2 ? _assetConfig : _assetConfig['0'];
573
588
  const timeDelta = assetConfig.last_updated;
574
589
  const lastFullUtilizationRate = assetConfig.last_full_utilization_rate;
575
- const totalSupply = (new Web3Number((Number(assetConfig.total_nominal_debt) / 1e18).toFixed(9), asset.decimals)).plus(Web3Number.fromWei(assetConfig.reserve, asset.decimals));
590
+ const currentDebt = new Web3Number((Number(assetConfig.total_nominal_debt) / 1e18).toFixed(9), asset.decimals);
591
+ const totalSupply = currentDebt.plus(Web3Number.fromWei(assetConfig.reserve, asset.decimals));
576
592
 
577
593
  const ratePerSecond = BigInt(Math.round(maxBorrowAPY / 365 / 24 / 60 / 60 * Number(SCALE)));
578
594
  const maxUtilisation = this.getMaxUtilizationGivenRatePerSecond(interestRateConfig, ratePerSecond, timeDelta, lastFullUtilizationRate);
579
595
  logger.verbose(`${asset.symbol}::VesuAdapter::getMaxBorrowableByInterestRate maxUtilisation: ${Number(maxUtilisation) / 1e18}, totalSupply: ${totalSupply.toString()}`);
580
596
 
581
597
  const maxDebtToHave = totalSupply.multipliedBy(Number(maxUtilisation) / 1e18);
582
- const currentDebt = new Web3Number((Number(assetConfig.total_nominal_debt) / 1e18).toFixed(9), asset.decimals);
598
+ logger.verbose(`${asset.symbol}::VesuAdapter::getMaxBorrowableByInterestRate currentDebt: ${currentDebt.toString()}, maxDebtToHave: ${maxDebtToHave.toString()}`);
583
599
  return maxDebtToHave.minus(currentDebt);
584
600
  }
585
601
 
586
- async getLTVConfig(config: IConfig) {
587
- const CACHE_KEY = 'ltv_config';
602
+ async getLTVConfig(config: IConfig, blockNumber: BlockIdentifier = 'latest') {
603
+ const CACHE_KEY = `ltv_config_${blockNumber}`;
588
604
  const cacheData = this.getCache<number>(CACHE_KEY);
589
605
  if (cacheData) {
590
606
  return cacheData as number;
@@ -592,10 +608,10 @@ export class VesuAdapter extends BaseAdapter {
592
608
  const { contract, isV2 } = await this.getVesuSingletonContract(config, this.config.poolId);
593
609
  let ltv = 0;
594
610
  if (isV2) {
595
- const output: any = await contract.call('pair_config', [this.config.collateral.address.address, this.config.debt.address.address]);
611
+ const output: any = await contract.call('pair_config', [this.config.collateral.address.address, this.config.debt.address.address], { blockIdentifier: blockNumber });
596
612
  ltv = Number(output.max_ltv) / 1e18;
597
613
  } else {
598
- const output: any = await contract.call('ltv_config', [this.config.poolId.address, this.config.collateral.address.address, this.config.debt.address.address]);
614
+ const output: any = await contract.call('ltv_config', [this.config.poolId.address, this.config.collateral.address.address, this.config.debt.address.address], { blockIdentifier: blockNumber });
599
615
  ltv = Number(output.max_ltv) / 1e18;
600
616
  }
601
617
  if (ltv == 0) {
@@ -605,12 +621,12 @@ export class VesuAdapter extends BaseAdapter {
605
621
  return this.getCache<number>(CACHE_KEY) as number;
606
622
  }
607
623
 
608
- async getPositions(config: IConfig): Promise<VaultPosition[]> {
624
+ async getPositions(config: IConfig, blockNumber: BlockIdentifier = 'latest'): Promise<VaultPosition[]> {
609
625
  if (!this.pricer) {
610
626
  throw new Error('Pricer is not initialized');
611
627
  }
612
628
  // { '0': { collateral_shares: 0n, nominal_debt: 0n }, '1': 0n, '2': 0n }
613
- const CACHE_KEY = 'positions';
629
+ const CACHE_KEY = `positions_${blockNumber}`;
614
630
  const cacheData = this.getCache<VaultPosition[]>(CACHE_KEY);
615
631
  if (cacheData) {
616
632
  return cacheData;
@@ -623,8 +639,9 @@ export class VesuAdapter extends BaseAdapter {
623
639
  this.config.collateral.address.address,
624
640
  this.config.debt.address.address,
625
641
  this.config.vaultAllocator.address
626
- ]);
642
+ ], { blockIdentifier: blockNumber });
627
643
 
644
+ console.log(output)
628
645
  const token1Price = await this.pricer.getPrice(this.config.collateral.symbol);
629
646
  const token2Price = await this.pricer.getPrice(this.config.debt.symbol);
630
647
  logger.verbose(`VesuAdapter::getPositions token1Price: ${token1Price.price}, token2Price: ${token2Price.price}`);
@@ -646,12 +663,12 @@ export class VesuAdapter extends BaseAdapter {
646
663
  return value;
647
664
  }
648
665
 
649
- async getCollateralization(config: IConfig): Promise<Omit<VaultPosition, 'amount'>[]> {
666
+ async getCollateralization(config: IConfig, blockNumber: BlockIdentifier = 'latest'): Promise<Omit<VaultPosition, 'amount'>[]> {
650
667
  if (!this.pricer) {
651
668
  throw new Error('Pricer is not initialized');
652
669
  }
653
670
  // { '0': bool, '1': 0n, '2': 0n }
654
- const CACHE_KEY = 'collateralization';
671
+ const CACHE_KEY = `collateralization_${blockNumber}`;
655
672
  const cacheData = this.getCache<Omit<VaultPosition, 'amount'>[]>(CACHE_KEY);
656
673
  if (cacheData) {
657
674
  return cacheData;
@@ -664,7 +681,7 @@ export class VesuAdapter extends BaseAdapter {
664
681
  this.config.collateral.address.address,
665
682
  this.config.debt.address.address,
666
683
  this.config.vaultAllocator.address
667
- ]);
684
+ ], { blockIdentifier: blockNumber });
668
685
 
669
686
  // usd values
670
687
  const collateralAmount = Web3Number.fromWei(output['1'].toString(), 18);
@@ -709,9 +726,9 @@ export class VesuAdapter extends BaseAdapter {
709
726
  }
710
727
  }
711
728
 
712
- async getHealthFactor() {
713
- const ltv = await this.getLTVConfig(this.networkConfig!);
714
- const collateralisation = await this.getCollateralization(this.networkConfig!);
729
+ async getHealthFactor(blockNumber: BlockIdentifier = 'latest') {
730
+ const ltv = await this.getLTVConfig(this.networkConfig!, blockNumber);
731
+ const collateralisation = await this.getCollateralization(this.networkConfig!, blockNumber);
715
732
  return collateralisation[0].usdValue * ltv / collateralisation[1].usdValue;
716
733
  }
717
734
 
@@ -0,0 +1,322 @@
1
+ import { ContractAddr, Web3Number } from "@/dataTypes";
2
+ import { IConfig, TokenInfo } from "@/interfaces";
3
+ import { PricerBase } from "@/modules/pricerBase";
4
+ import { BaseAdapter, BaseAdapterConfig, SupportedPosition, PositionInfo, PositionAPY, APYType, ManageCall, AdapterLeafType, GenerateCallFn } from "./baseAdapter";
5
+ import { SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
6
+ import { hash, uint256, Contract } from "starknet";
7
+ import { VesuAdapter } from "./vesu-adapter";
8
+ import { logger } from "@/utils";
9
+
10
+ export interface VesuSupplyOnlyAdapterConfig extends BaseAdapterConfig {
11
+ vTokenContract: ContractAddr;
12
+ }
13
+
14
+ export interface DepositParams {
15
+ amount: Web3Number;
16
+ }
17
+
18
+ export interface WithdrawParams {
19
+ amount: Web3Number;
20
+ }
21
+
22
+ export class VesuSupplyOnlyAdapter extends BaseAdapter<any, any> {
23
+ readonly config: VesuSupplyOnlyAdapterConfig;
24
+
25
+ constructor(config: VesuSupplyOnlyAdapterConfig) {
26
+ super(config);
27
+ this.config = config;
28
+ }
29
+
30
+ protected async getAPY(supportedPosition: SupportedPosition): Promise<PositionAPY> {
31
+ const CACHE_KEY = `apy_${this.config.vTokenContract.address}`;
32
+ const cacheData = this.getCache<PositionAPY>(CACHE_KEY);
33
+ if (cacheData) {
34
+ return cacheData;
35
+ }
36
+
37
+ try {
38
+ // Get Vesu pools to find APY for the underlying asset
39
+ const allVesuPools = await VesuAdapter.getVesuPools();
40
+ const baseToken = this.config.baseToken;
41
+
42
+ // Find the pool that contains our base token
43
+ const pool = allVesuPools.pools.find(p => {
44
+ return p.assets.some((asset: any) =>
45
+ asset.symbol.toLowerCase() === baseToken.symbol.toLowerCase()
46
+ );
47
+ });
48
+
49
+ if (!pool) {
50
+ logger.warn(`VesuSupplyOnlyAdapter: Pool not found for token ${baseToken.symbol}`);
51
+ return {
52
+ apy: 0,
53
+ type: APYType.BASE
54
+ };
55
+ }
56
+
57
+ // Find the asset stats for our token
58
+ const assetStats = pool.assets.find((a: any) =>
59
+ a.symbol.toLowerCase() === baseToken.symbol.toLowerCase()
60
+ )?.stats;
61
+
62
+ if (!assetStats) {
63
+ logger.warn(`VesuSupplyOnlyAdapter: Asset stats not found for token ${baseToken.symbol}`);
64
+ return {
65
+ apy: 0,
66
+ type: APYType.BASE
67
+ };
68
+ }
69
+
70
+ // Get supply APY (divide by 1e18 as it's in wei)
71
+ const supplyApy = Number(assetStats.supplyApy?.value || 0) / 1e18;
72
+
73
+ // Get LST APR if applicable (for LST tokens)
74
+ let lstAPY = 0;
75
+ if (baseToken.symbol === 'STRK' || baseToken.symbol === 'ETH') {
76
+ // This would need to be implemented based on your LST APR service
77
+ // For now, using a placeholder
78
+ lstAPY = 0;
79
+ }
80
+
81
+ const totalAPY = supplyApy + lstAPY;
82
+
83
+ const result = {
84
+ apy: totalAPY,
85
+ type: APYType.BASE
86
+ };
87
+
88
+ this.setCache(CACHE_KEY, result, 300000); // Cache for 5 minutes
89
+ return result;
90
+ } catch (error) {
91
+ logger.error(`VesuSupplyOnlyAdapter: Error getting APY for ${supportedPosition.asset.symbol}:`, error);
92
+ return {
93
+ apy: 0,
94
+ type: APYType.BASE
95
+ };
96
+ }
97
+ }
98
+
99
+ protected async getPosition(supportedPosition: SupportedPosition): Promise<Web3Number> {
100
+ const CACHE_KEY = `position_${this.config.vTokenContract.address}`;
101
+ const cacheData = this.getCache<Web3Number>(CACHE_KEY);
102
+ if (cacheData) {
103
+ return cacheData;
104
+ }
105
+
106
+ try {
107
+ // Create contract instance for the vToken
108
+ const vTokenContract = new Contract({
109
+ abi: [], // We only need basic ERC20 methods
110
+ address: this.config.vTokenContract.address,
111
+ providerOrAccount: this.config.networkConfig.provider
112
+ });
113
+
114
+ // Get the vault allocator's balance (shares) in the vToken contract
115
+ const shares = await vTokenContract.balanceOf(this.config.vaultAllocator.address);
116
+
117
+ // Convert shares to assets using convert_to_assets
118
+ const assets = await vTokenContract.convert_to_assets(
119
+ uint256.bnToUint256(shares)
120
+ );
121
+
122
+ const result = Web3Number.fromWei(
123
+ assets.toString(),
124
+ supportedPosition.asset.decimals
125
+ );
126
+
127
+ this.setCache(CACHE_KEY, result, 60000); // Cache for 1 minute
128
+ return result;
129
+ } catch (error) {
130
+ logger.error(`VesuSupplyOnlyAdapter: Error getting position for ${supportedPosition.asset.symbol}:`, error);
131
+ return new Web3Number('0', supportedPosition.asset.decimals);
132
+ }
133
+ }
134
+
135
+ protected async maxDeposit(amount?: Web3Number): Promise<PositionInfo[]> {
136
+ const baseToken = this.config.baseToken;
137
+ // todo for assets with some borrowing on Vesu, the yield wont
138
+ // remain same as supply increases. So need to account for that.
139
+
140
+ if (!amount) {
141
+ // Return infinity for max deposit when no amount specified
142
+ return [{
143
+ amount: new Web3Number('999999999999999999999999999', baseToken.decimals),
144
+ usdValue: 999999999999999999999999999,
145
+ remarks: "Max deposit (infinity)",
146
+ apy: await this.getAPY({ asset: baseToken, isDebt: false })
147
+ }];
148
+ }
149
+
150
+ // Return position info based on input amount
151
+ const usdValue = await this.getUSDValue(baseToken, amount);
152
+ return [{
153
+ amount,
154
+ usdValue,
155
+ remarks: "Deposit amount",
156
+ apy: await this.getAPY({ asset: baseToken, isDebt: false })
157
+ }];
158
+ }
159
+
160
+ protected async maxWithdraw(): Promise<PositionInfo[]> {
161
+ const baseToken = this.config.baseToken;
162
+ const currentPosition = await this.getPosition({ asset: baseToken, isDebt: false });
163
+
164
+ // Return the current position as max withdraw
165
+ const usdValue = await this.getUSDValue(baseToken, currentPosition);
166
+ return [{
167
+ amount: currentPosition,
168
+ usdValue,
169
+ remarks: "Max withdraw",
170
+ apy: await this.getAPY({ asset: baseToken, isDebt: false })
171
+ }];
172
+ }
173
+
174
+ protected _getDepositLeaf(): {
175
+ target: ContractAddr,
176
+ method: string,
177
+ packedArguments: bigint[],
178
+ sanitizer: ContractAddr,
179
+ id: string
180
+ }[] {
181
+ const baseToken = this.config.baseToken;
182
+ const vTokenContract = this.config.vTokenContract;
183
+
184
+ return [
185
+ // Approval step
186
+ {
187
+ target: baseToken.address,
188
+ method: 'approve',
189
+ packedArguments: [
190
+ vTokenContract.toBigInt(), // spender
191
+ ],
192
+ sanitizer: SIMPLE_SANITIZER,
193
+ id: `approve_deposit_vtoken_${this.config.vTokenContract.address}`
194
+ },
195
+ // Deposit step
196
+ {
197
+ target: vTokenContract,
198
+ method: 'deposit',
199
+ packedArguments: [
200
+ this.config.vaultAllocator.toBigInt(),
201
+ ],
202
+ sanitizer: SIMPLE_SANITIZER,
203
+ id: `deposit_vtoken_${this.config.vTokenContract.address}`
204
+ }
205
+ ];
206
+ }
207
+
208
+ protected _getWithdrawLeaf(): {
209
+ target: ContractAddr,
210
+ method: string,
211
+ packedArguments: bigint[],
212
+ sanitizer: ContractAddr,
213
+ id: string
214
+ }[] {
215
+ const vTokenContract = this.config.vTokenContract;
216
+
217
+ return [
218
+ // Withdraw step
219
+ {
220
+ target: vTokenContract,
221
+ method: 'withdraw',
222
+ packedArguments: [
223
+ this.config.vaultAllocator.toBigInt(),
224
+ this.config.vaultAllocator.toBigInt(),
225
+ ],
226
+ sanitizer: SIMPLE_SANITIZER,
227
+ id: `withdraw_vtoken_${this.config.vTokenContract.address}`
228
+ }
229
+ ];
230
+ }
231
+
232
+ getDepositAdapter(): AdapterLeafType<DepositParams> {
233
+ const leafConfigs = this._getDepositLeaf();
234
+ const leaves = leafConfigs.map(config => {
235
+ const { target, method, packedArguments, sanitizer, id } = config;
236
+ const leaf = this.constructSimpleLeafData({
237
+ id: id,
238
+ target,
239
+ method,
240
+ packedArguments
241
+ }, sanitizer);
242
+ return leaf;
243
+ });
244
+ return { leaves, callConstructor: this.createDepositCall.bind(this) as unknown as GenerateCallFn<DepositParams> };
245
+ }
246
+
247
+ getWithdrawAdapter(): AdapterLeafType<WithdrawParams> {
248
+ const leafConfigs = this._getWithdrawLeaf();
249
+ const leaves = leafConfigs.map(config => {
250
+ const { target, method, packedArguments, sanitizer, id } = config;
251
+ const leaf = this.constructSimpleLeafData({
252
+ id: id,
253
+ target,
254
+ method,
255
+ packedArguments
256
+ }, sanitizer);
257
+ return leaf;
258
+ });
259
+ return { leaves, callConstructor: this.createWithdrawCall.bind(this) as unknown as GenerateCallFn<WithdrawParams> };
260
+ }
261
+
262
+ private createDepositCall(params: DepositParams): ManageCall[] {
263
+ const baseToken = this.config.baseToken;
264
+ const vTokenContract = this.config.vTokenContract;
265
+
266
+ const amount = params.amount;
267
+ const uint256Amount = uint256.bnToUint256(amount.toWei());
268
+
269
+ return [
270
+ // Approval call
271
+ {
272
+ sanitizer: SIMPLE_SANITIZER,
273
+ call: {
274
+ contractAddress: baseToken.address,
275
+ selector: hash.getSelectorFromName('approve'),
276
+ calldata: [
277
+ vTokenContract.toBigInt(), // spender
278
+ toBigInt(uint256Amount.low.toString()), // amount low
279
+ toBigInt(uint256Amount.high.toString()), // amount high
280
+ ]
281
+ }
282
+ },
283
+ // Deposit call
284
+ {
285
+ sanitizer: SIMPLE_SANITIZER,
286
+ call: {
287
+ contractAddress: vTokenContract,
288
+ selector: hash.getSelectorFromName('deposit'),
289
+ calldata: [
290
+ toBigInt(uint256Amount.low.toString()), // amount low
291
+ toBigInt(uint256Amount.high.toString()), // amount high
292
+ this.config.vaultAllocator.toBigInt(),
293
+ ]
294
+ }
295
+ }
296
+ ];
297
+ }
298
+
299
+ private createWithdrawCall(params: WithdrawParams): ManageCall[] {
300
+ const vTokenContract = this.config.vTokenContract;
301
+
302
+ const amount = params.amount;
303
+ const uint256Amount = uint256.bnToUint256(amount.toWei());
304
+
305
+ return [
306
+ // Withdraw call
307
+ {
308
+ sanitizer: SIMPLE_SANITIZER,
309
+ call: {
310
+ contractAddress: vTokenContract,
311
+ selector: hash.getSelectorFromName('withdraw'),
312
+ calldata: [
313
+ toBigInt(uint256Amount.low.toString()), // amount low
314
+ toBigInt(uint256Amount.high.toString()), // amount high
315
+ this.config.vaultAllocator.toBigInt(),
316
+ this.config.vaultAllocator.toBigInt(),
317
+ ]
318
+ }
319
+ }
320
+ ];
321
+ }
322
+ }