@strkfarm/sdk 2.0.0-dev.34 → 2.0.0-dev.35

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.
@@ -6,6 +6,7 @@ export * from './sensei';
6
6
  export * from './universal-adapters';
7
7
  export * from './universal-strategy';
8
8
  export * from './universal-lst-muliplier-strategy';
9
+ export * from './usdc-boosted-strategy';
9
10
  export * from './vesu-extended-strategy/vesu-extended-strategy';
10
11
  export * from './vesu-extended-strategy/utils/config.runtime';
11
12
  export * from './vesu-extended-strategy/utils/helper';
@@ -121,9 +121,10 @@ export abstract class SVKStrategy<S extends UniversalStrategySettings>
121
121
  adapter: { getProofs: (isDeposit: boolean, tree: any) => any },
122
122
  isDeposit: boolean,
123
123
  amount: Web3Number,
124
+ additionalParams?: Record<string, any>,
124
125
  ): Promise<Call> {
125
126
  const proofsInfo = adapter.getProofs(isDeposit, this.getMerkleTree());
126
- const manageCalls = await proofsInfo.callConstructor({ amount });
127
+ const manageCalls = await proofsInfo.callConstructor({ amount, ...additionalParams });
127
128
  return this.getManageCall(
128
129
  this.getProofGroupsForManageCalls(manageCalls),
129
130
  manageCalls,
@@ -293,7 +294,7 @@ export abstract class SVKStrategy<S extends UniversalStrategySettings>
293
294
  const netAPY = weightedAPYs / totalHoldingsUSDValue;
294
295
  return {
295
296
  net: netAPY,
296
- splits: allPositions.map(p => ({apy: p.apy.apy, id: p.remarks ?? ''}))
297
+ splits: allPositions.map(p => ({ apy: p.apy.apy, id: p.remarks ?? '' }))
297
298
  };
298
299
  }
299
300
 
@@ -2,6 +2,8 @@ import { ContractAddr } from "@/dataTypes";
2
2
 
3
3
  // Zellic audited
4
4
  export const SIMPLE_SANITIZER = ContractAddr.from('0x5a2e3ceb3da368b983a8717898427ab7b6daf04014b70f321e777f9aad940b4');
5
+ // Above SIMPLE_SANITIZER was not exposing the request_redeem method
6
+ export const SVK_SIMPLE_SANITIZER = ContractAddr.from('0x03dcde04343257c3ce14574676cb9c5b2eda16e332c1b8caf5dc4c95ac568d2f')
5
7
  export const EXTENDED_SANITIZER = ContractAddr.from('0x65891708362b24dcf4c40c8e218cce6e82d1d6b3a3404c9ab00a48f08e2c110');
6
8
  export const AVNU_LEGACY_SANITIZER = ContractAddr.from('0x0656fBE853f116DD53956176a553eDe8fE65632252f8aceB50C1B9B6c8237309');
7
9
  // Without flashloan options
@@ -94,6 +94,7 @@ export class AvnuAdapter extends BaseAdapter<DepositParams, WithdrawParams> {
94
94
  params: DepositParams | WithdrawParams,
95
95
  fromToken: TokenInfo,
96
96
  toToken: TokenInfo,
97
+ // NOTE : this is a direction flag that we are swapping in this direction
97
98
  usdcToBtc: boolean,
98
99
  ): Promise<ManageCall[]> {
99
100
  const vaultAllocator = ContractAddr.from(this.config.vaultAllocator.address);
@@ -121,8 +122,8 @@ export class AvnuAdapter extends BaseAdapter<DepositParams, WithdrawParams> {
121
122
  };
122
123
  logger.verbose(
123
124
  `${AvnuAdapter.name}::buildSwapCalls stored price info: ` +
124
- `${fromAmt} ${fromToken.symbol} → ${toAmt} ${toToken.symbol}, ` +
125
- `effectivePrice=${this.lastSwapPriceInfo.effectivePrice}`,
125
+ `${fromAmt} ${fromToken.symbol} → ${toAmt} ${toToken.symbol}, ` +
126
+ `effectivePrice=${this.lastSwapPriceInfo.effectivePrice}`,
126
127
  );
127
128
 
128
129
  const getCalldata = await this.avnuWrapper.getSwapCallData(
@@ -24,6 +24,7 @@ import {
24
24
  import ExtendedWrapper, {
25
25
  AssetOperationStatus,
26
26
  AssetOperationType,
27
+ FundingPayment,
27
28
  FundingRate,
28
29
  OrderSide,
29
30
  Position,
@@ -127,6 +128,30 @@ export class ExtendedAdapter extends BaseAdapter<
127
128
  }
128
129
  }
129
130
 
131
+ /** Account funding payment history via `GET /api/v1/account/funding-payments` (USDC amounts). */
132
+ public async getFundingPayments(
133
+ side: string,
134
+ startTime?: number,
135
+ limit?: number,
136
+ ): Promise<{ success: boolean; data: FundingPayment[] }> {
137
+ try {
138
+ const response = await this.client.getUserFundingPayments(
139
+ this.config.extendedMarketName,
140
+ side,
141
+ startTime ?? Date.now() - 30 * 24 * 60 * 60 * 1000,
142
+ limit ?? 200,
143
+ );
144
+ if (response.status !== "OK") {
145
+ logger.error("error getting funding payments", response.data);
146
+ return { success: false, data: [] };
147
+ }
148
+ return { success: true, data: response.data ?? [] };
149
+ } catch (err) {
150
+ logger.error("error getting funding payments", err);
151
+ return { success: false, data: [] };
152
+ }
153
+ }
154
+
130
155
  protected async getPosition(
131
156
  supportedPosition: SupportedPosition,
132
157
  ): Promise<PositionAmount> {
@@ -14,11 +14,12 @@ import {
14
14
  WithdrawParams,
15
15
  PositionAmount,
16
16
  } from "./baseAdapter";
17
- import { SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
17
+ import { SIMPLE_SANITIZER, SVK_SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
18
18
  import { hash, uint256, Contract } from "starknet";
19
19
  import { logger } from "@/utils";
20
20
  // Troves SVK universal vault ABI: ERC-4626 + SVK views (e.g. due_assets_from_owner).
21
21
  import universalVaultAbi from "@/data/universal-vault.abi.json";
22
+ import redeemRequestNftAbi from "@/data/redeem-request-nft.abi.json";
22
23
 
23
24
  /** Public Troves strategies feed (APY + metadata). Override in config for tests. */
24
25
  export const DEFAULT_TROVES_STRATEGIES_API = "https://app.troves.fi/api/strategies";
@@ -189,6 +190,131 @@ export class SvkTrovesAdapter extends BaseAdapter<DepositParams, WithdrawParams>
189
190
  }
190
191
  }
191
192
 
193
+ async getPendingAssetsFromOwner(
194
+ owner: ContractAddr = this._positionOwner(),
195
+ decimals: number = this.config.baseToken.decimals,
196
+ ): Promise<Web3Number> {
197
+ const vault = new Contract({
198
+ abi: universalVaultAbi as never,
199
+ address: this.config.strategyVault.address,
200
+ providerOrAccount: this.config.networkConfig.provider,
201
+ });
202
+
203
+ try {
204
+ const dueRaw: any = await vault.call("due_assets_from_owner", [owner.address]);
205
+ return Web3Number.fromWei(dueRaw.toString(), decimals);
206
+ } catch (e) {
207
+ logger.warn(
208
+ `${SvkTrovesAdapter.name}::getPendingAssetsFromOwner: due_assets_from_owner failed (treating as 0): ${(e as Error).message}`,
209
+ );
210
+ return Web3Number.fromWei("0", decimals);
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Get pending assets from owner by scanning redeem request NFTs.
216
+ * This method iterates backwards through NFT IDs to find all pending redemptions for a specific owner.
217
+ *
218
+ * @param redeemRequestNFT - The redeem request NFT contract address
219
+ * @param owner - The owner address to check for pending redemptions (defaults to positionOwner)
220
+ * @param decimals - Token decimals for conversion (defaults to baseToken decimals)
221
+ * @returns Total pending assets from all NFTs owned by the specified address
222
+ */
223
+ async getPendingAssetsFromOwnerNFTMethod(
224
+ redeemRequestNFT: ContractAddr,
225
+ owner: ContractAddr = this._positionOwner(),
226
+ decimals: number = this.config.baseToken.decimals,
227
+ ): Promise<Web3Number> {
228
+ try {
229
+ const nftContract = new Contract({
230
+ abi: redeemRequestNftAbi as never,
231
+ address: redeemRequestNFT.address,
232
+ providerOrAccount: this.config.networkConfig.provider,
233
+ });
234
+
235
+ // Step 1: Get the latest NFT ID (the one that is not yet used)
236
+ const idLenRaw: any = await nftContract.id_len();
237
+ const latestId = BigInt(idLenRaw.toString());
238
+
239
+ if (latestId === 0n) {
240
+ logger.info(
241
+ `${SvkTrovesAdapter.name}::getPendingAssetsFromOwnerNFTMethod: No NFTs minted yet`,
242
+ );
243
+ return Web3Number.fromWei("0", decimals);
244
+ }
245
+
246
+ // Step 2: Start from (latestId - 1) and loop backwards to find all pending NFTs
247
+ const matchingIds: bigint[] = [];
248
+ let currentId = latestId - 1n;
249
+
250
+ while (currentId >= 0n) {
251
+ try {
252
+ // Convert currentId to u256 format
253
+ const idU256 = uint256.bnToUint256(currentId);
254
+ const ownerRaw: any = await nftContract.owner_of(idU256);
255
+
256
+ // Step 3: Filter IDs that have the owner matching our parameter
257
+ // Use ContractAddr.from to normalize addresses before comparison
258
+ const nftOwnerAddr = ContractAddr.from(ownerRaw.toString());
259
+ if (nftOwnerAddr.eq(owner)) {
260
+ matchingIds.push(currentId);
261
+ logger.debug(
262
+ `${SvkTrovesAdapter.name}::getPendingAssetsFromOwnerNFTMethod: Found matching NFT ID ${currentId} for owner ${owner.address}`,
263
+ );
264
+ }
265
+
266
+ currentId--;
267
+ } catch (e) {
268
+ // When owner_of throws an error, it means we've reached the last pending NFT
269
+ logger.info(
270
+ `${SvkTrovesAdapter.name}::getPendingAssetsFromOwnerNFTMethod: Reached last pending NFT at id ${currentId}`,
271
+ );
272
+ break;
273
+ }
274
+ }
275
+
276
+ if (matchingIds.length === 0) {
277
+ logger.info(
278
+ `${SvkTrovesAdapter.name}::getPendingAssetsFromOwnerNFTMethod: No matching NFTs found for owner ${owner.address}`,
279
+ );
280
+ return Web3Number.fromWei("0", decimals);
281
+ }
282
+
283
+ // Step 4: For each matching ID, call id_to_info and sum up all nominal values
284
+ let totalNominal = 0n;
285
+
286
+ for (const nftId of matchingIds) {
287
+ try {
288
+ const idU256 = uint256.bnToUint256(nftId);
289
+ const infoRaw: any = await nftContract.id_to_info(idU256);
290
+
291
+ // The response is a struct with { epoch: u256, nominal: u256 }
292
+ const nominal = BigInt(infoRaw.nominal.toString());
293
+ totalNominal += nominal;
294
+
295
+ logger.debug(
296
+ `${SvkTrovesAdapter.name}::getPendingAssetsFromOwnerNFTMethod: NFT ID ${nftId} has nominal ${nominal}`,
297
+ );
298
+ } catch (e) {
299
+ logger.warn(
300
+ `${SvkTrovesAdapter.name}::getPendingAssetsFromOwnerNFTMethod: Failed to get info for NFT ID ${nftId}: ${(e as Error).message}`,
301
+ );
302
+ }
303
+ }
304
+
305
+ logger.info(
306
+ `${SvkTrovesAdapter.name}::getPendingAssetsFromOwnerNFTMethod: Found ${matchingIds.length} NFTs with total nominal ${totalNominal}`,
307
+ );
308
+
309
+ return Web3Number.fromWei(totalNominal.toString(), decimals);
310
+ } catch (error) {
311
+ logger.error(
312
+ `${SvkTrovesAdapter.name}::getPendingAssetsFromOwnerNFTMethod: ${(error as Error).message}`,
313
+ );
314
+ return Web3Number.fromWei("0", decimals);
315
+ }
316
+ }
317
+
192
318
  async maxDeposit(amount?: Web3Number): Promise<PositionInfo> {
193
319
  const baseToken = this.config.baseToken;
194
320
  if (!amount) {
@@ -270,9 +396,9 @@ export class SvkTrovesAdapter extends BaseAdapter<DepositParams, WithdrawParams>
270
396
  return [
271
397
  {
272
398
  target: strategyVault,
273
- method: "withdraw",
399
+ method: "request_redeem",
274
400
  packedArguments: [recv.toBigInt(), owner.toBigInt()],
275
- sanitizer: SIMPLE_SANITIZER,
401
+ sanitizer: SVK_SIMPLE_SANITIZER,
276
402
  id: this._withdrawCallProofReadableId(),
277
403
  },
278
404
  ];
@@ -340,16 +466,24 @@ export class SvkTrovesAdapter extends BaseAdapter<DepositParams, WithdrawParams>
340
466
  const recv = this.config.vaultAllocator;
341
467
  const owner = this.config.vaultAllocator;
342
468
 
469
+ const vault = new Contract({
470
+ abi: universalVaultAbi as never,
471
+ address: strategyVault.address,
472
+ providerOrAccount: this.config.networkConfig.provider,
473
+ });
474
+ const sharesRaw: any = await vault.convert_to_shares(uint256Amount);
475
+ const sharesU256 = uint256.bnToUint256(sharesRaw.toString());
476
+
343
477
  return [
344
478
  {
345
479
  proofReadableId: this._withdrawCallProofReadableId(),
346
- sanitizer: SIMPLE_SANITIZER,
480
+ sanitizer: SVK_SIMPLE_SANITIZER,
347
481
  call: {
348
482
  contractAddress: strategyVault,
349
- selector: hash.getSelectorFromName("withdraw"),
483
+ selector: hash.getSelectorFromName("request_redeem"),
350
484
  calldata: [
351
- toBigInt(uint256Amount.low.toString()),
352
- toBigInt(uint256Amount.high.toString()),
485
+ toBigInt(sharesU256.low.toString()),
486
+ toBigInt(sharesU256.high.toString()),
353
487
  recv.toBigInt(),
354
488
  owner.toBigInt(),
355
489
  ],
@@ -198,9 +198,19 @@ export class VesuModifyPositionAdapter extends BaseAdapter<
198
198
  if (!helperOutput || helperOutput.lessThan(0)) {
199
199
  throw new Error(`Failed to calculate default deposit debt delta: ${helperOutput?.toNumber()}`);
200
200
  }
201
+ // Fixed :
202
+ // getMaxDebtAmount returns the TOTAL target debt at the new collateral level.
203
+ // Subtract current debt to get the incremental borrow delta.
201
204
  const normalizedDebtAmount = this._normalizeDebtAmountFromHelper(
202
205
  helperOutput,
203
- );
206
+ ).minus(state.currentDebt);
207
+ if (normalizedDebtAmount.lessThan(0)) {
208
+ logger.warn(`VesuModifyPositionAdapter: deposit debt delta is negative (${normalizedDebtAmount.toNumber()}), clamping to zero`);
209
+ return {
210
+ collateral: this._toSigned(collateralToAdd, false),
211
+ debt: this._toSigned(Web3Number.fromWei(0, this.config.debt.decimals), false),
212
+ };
213
+ }
204
214
  return {
205
215
  collateral: this._toSigned(collateralToAdd, false),
206
216
  debt: this._toSigned(normalizedDebtAmount, false),
@@ -221,8 +231,8 @@ export class VesuModifyPositionAdapter extends BaseAdapter<
221
231
  state.debtPrice,
222
232
  this.config.debt,
223
233
  );
224
- if (!helperOutput || helperOutput.greaterThan(0)) {
225
- throw new Error(`Failed to calculate default withdraw debt delta: ${helperOutput?.toNumber()}`);
234
+ if (!helperOutput || helperOutput.lessThan(0)) {
235
+ throw new Error(`Failed to calculate max debt amount for withdraw: ${helperOutput?.toNumber()}`);
226
236
  }
227
237
  const normalizedDebtAmount = this._normalizeDebtAmountFromHelper(
228
238
  helperOutput,
@@ -262,21 +272,21 @@ export class VesuModifyPositionAdapter extends BaseAdapter<
262
272
  const call = contract.populate("modify_position", {
263
273
  params: isV2
264
274
  ? {
265
- collateral_asset: this.config.collateral.address.toBigInt(),
266
- debt_asset: this.config.debt.address.toBigInt(),
267
- user: this.config.vaultAllocator.toBigInt(),
268
- collateral: this._amountStruct(collateralDelta),
269
- debt: this._amountStruct(debtDelta),
270
- }
275
+ collateral_asset: this.config.collateral.address.toBigInt(),
276
+ debt_asset: this.config.debt.address.toBigInt(),
277
+ user: this.config.vaultAllocator.toBigInt(),
278
+ collateral: this._amountStruct(collateralDelta),
279
+ debt: this._amountStruct(debtDelta),
280
+ }
271
281
  : {
272
- pool_id: this.config.poolId.toBigInt(),
273
- collateral_asset: this.config.collateral.address.toBigInt(),
274
- debt_asset: this.config.debt.address.toBigInt(),
275
- user: this.config.vaultAllocator.toBigInt(),
276
- collateral: this._amountStruct(collateralDelta),
277
- debt: this._amountStruct(debtDelta),
278
- data: [0],
279
- },
282
+ pool_id: this.config.poolId.toBigInt(),
283
+ collateral_asset: this.config.collateral.address.toBigInt(),
284
+ debt_asset: this.config.debt.address.toBigInt(),
285
+ user: this.config.vaultAllocator.toBigInt(),
286
+ collateral: this._amountStruct(collateralDelta),
287
+ debt: this._amountStruct(debtDelta),
288
+ data: [0],
289
+ },
280
290
  });
281
291
  return {
282
292
  proofReadableId,
@@ -337,18 +347,18 @@ export class VesuModifyPositionAdapter extends BaseAdapter<
337
347
  const { addr, isV2 } = getVesuSingletonAddress(this.config.poolId);
338
348
  const modifyPackedArguments = isV2
339
349
  ? [
340
- this.config.collateral.address.toBigInt(),
341
- this.config.debt.address.toBigInt(),
342
- this.config.vaultAllocator.toBigInt(),
343
- ]
350
+ this.config.collateral.address.toBigInt(),
351
+ this.config.debt.address.toBigInt(),
352
+ this.config.vaultAllocator.toBigInt(),
353
+ ]
344
354
  : [
345
- this.config.poolId.toBigInt(),
346
- this.config.collateral.address.toBigInt(),
347
- this.config.debt.address.toBigInt(),
348
- this.config.vaultAllocator.toBigInt(),
349
- 1n,
350
- 0n,
351
- ];
355
+ this.config.poolId.toBigInt(),
356
+ this.config.collateral.address.toBigInt(),
357
+ this.config.debt.address.toBigInt(),
358
+ this.config.vaultAllocator.toBigInt(),
359
+ 1n,
360
+ 0n,
361
+ ];
352
362
 
353
363
  return [
354
364
  {
@@ -378,18 +388,18 @@ export class VesuModifyPositionAdapter extends BaseAdapter<
378
388
  const { addr, isV2 } = getVesuSingletonAddress(this.config.poolId);
379
389
  const modifyPackedArguments = isV2
380
390
  ? [
381
- this.config.collateral.address.toBigInt(),
382
- this.config.debt.address.toBigInt(),
383
- this.config.vaultAllocator.toBigInt(),
384
- ]
391
+ this.config.collateral.address.toBigInt(),
392
+ this.config.debt.address.toBigInt(),
393
+ this.config.vaultAllocator.toBigInt(),
394
+ ]
385
395
  : [
386
- this.config.poolId.toBigInt(),
387
- this.config.collateral.address.toBigInt(),
388
- this.config.debt.address.toBigInt(),
389
- this.config.vaultAllocator.toBigInt(),
390
- 1n,
391
- 0n,
392
- ];
396
+ this.config.poolId.toBigInt(),
397
+ this.config.collateral.address.toBigInt(),
398
+ this.config.debt.address.toBigInt(),
399
+ this.config.vaultAllocator.toBigInt(),
400
+ 1n,
401
+ 0n,
402
+ ];
393
403
 
394
404
  return [
395
405
  {
@@ -473,4 +483,24 @@ export class VesuModifyPositionAdapter extends BaseAdapter<
473
483
  this._prepareVesuAdapter();
474
484
  return this._vesuAdapter.getHealthFactor();
475
485
  }
486
+
487
+ /**
488
+ * Simulates a deposit of `depositAmount` collateral and returns how much
489
+ * debt (STRK) would be incrementally borrowed to reach the target LTV.
490
+ * Used upstream to size the AVNU swap call in the same transaction batch.
491
+ */
492
+ async getExpectedDepositDebtDelta(depositAmount: Web3Number): Promise<Web3Number> {
493
+ const defaults = await this._buildDefaultDepositDeltas({ amount: depositAmount });
494
+ return defaults.debt.amount;
495
+ }
496
+
497
+ /**
498
+ * Simulates a withdrawal of `withdrawAmount` collateral and returns the
499
+ * incremental debt delta needed to keep the target health factor.
500
+ * Positive means borrow, negative means repay.
501
+ */
502
+ async getExpectedWithdrawDebtDelta(withdrawAmount: Web3Number): Promise<Web3Number> {
503
+ const defaults = await this._buildDefaultWithdrawDeltas({ amount: withdrawAmount });
504
+ return defaults.debt.amount;
505
+ }
476
506
  }