@strkfarm/sdk 2.0.0-dev.28 → 2.0.0-dev.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strkfarm/sdk",
3
- "version": "2.0.0-dev.28",
3
+ "version": "2.0.0-dev.29",
4
4
  "description": "STRKFarm TS SDK (Meant for our internal use, but feel free to use it)",
5
5
  "typings": "dist/index.d.ts",
6
6
  "types": "dist/index.d.ts",
@@ -1,3 +1,4 @@
1
+ import BigNumber from "bignumber.js";
1
2
  import { _Web3Number } from "./_bignumber";
2
3
 
3
4
  export class Web3Number extends _Web3Number<Web3Number> {
@@ -5,4 +6,8 @@ export class Web3Number extends _Web3Number<Web3Number> {
5
6
  const bn = (new Web3Number(weiNumber, decimals)).dividedBy(10 ** decimals)
6
7
  return new Web3Number(bn.toString(), decimals);
7
8
  }
8
- }
9
+
10
+ static fromNumber(number: number, decimals: number) {
11
+ return new Web3Number(number.toString(), decimals);
12
+ }
13
+ }
@@ -8,6 +8,10 @@ export class Web3Number extends _Web3Number<Web3Number> {
8
8
  return new Web3Number(bn.toString(), decimals);
9
9
  }
10
10
 
11
+ static fromNumber(number: number, decimals: number) {
12
+ return new Web3Number(number.toString(), decimals);
13
+ }
14
+
11
15
  [util.inspect.custom](depth: any, opts: any): string {
12
16
  return this.toString();
13
17
  }
@@ -19,4 +23,4 @@ export class Web3Number extends _Web3Number<Web3Number> {
19
23
  inspect(depth: any, opts: any) {
20
24
  return this.toString();
21
25
  }
22
- }
26
+ }
@@ -414,6 +414,12 @@ const VaultProtocol: IProtocol = {
414
414
  name: "Vault",
415
415
  logo: ""
416
416
  }
417
+
418
+ const TrovesProtocol: IProtocol = {
419
+ name: "Troves",
420
+ logo: "https://app.troves.fi/favicon.ico"
421
+ };
422
+
417
423
  export const Protocols = {
418
424
  NONE: NoneProtocol,
419
425
  VESU: VesuProtocol,
@@ -421,5 +427,6 @@ export const Protocols = {
421
427
  EXTENDED: ExtendedProtocol,
422
428
  EKUBO: EkuboProtocol,
423
429
  AVNU: AvnuProtocol,
424
- VAULT: VaultProtocol
430
+ VAULT: VaultProtocol,
431
+ TROVES: TrovesProtocol
425
432
  }
@@ -17,7 +17,7 @@ export interface ManageCall {
17
17
  }
18
18
 
19
19
  export interface SwapPriceInfo {
20
- source: 'avnu' | 'ekubo';
20
+ source: 'avnu' | 'ekubo'; // | 'no-swap';
21
21
  fromTokenSymbol: string;
22
22
  toTokenSymbol: string;
23
23
  fromAmount: number;
@@ -56,7 +56,7 @@ export class ExtendedAdapter extends BaseAdapter<
56
56
  > {
57
57
  readonly config: ExtendedAdapterConfig;
58
58
  readonly client: ExtendedWrapper;
59
- readonly usdceToken: TokenInfo;
59
+ readonly usdcToken: TokenInfo;
60
60
  readonly retryDelayForOrderStatus: number;
61
61
  readonly minimumExtendedMovementAmount: number;
62
62
 
@@ -75,15 +75,15 @@ export class ExtendedAdapter extends BaseAdapter<
75
75
  this.client = client;
76
76
  this.retryDelayForOrderStatus =
77
77
  this.config.retryDelayForOrderStatus ?? 3000;
78
- this.usdceToken = this.config.supportedPositions[0].asset;
78
+ this.usdcToken = this.config.supportedPositions[0].asset;
79
79
  }
80
80
 
81
81
  private _depositApproveProofReadableId(): string {
82
- return `extended_approve_${this.usdceToken.symbol}`;
82
+ return `extended_approve_${this.usdcToken.symbol}`;
83
83
  }
84
84
 
85
85
  private _depositCallProofReadableId(): string {
86
- return `extended_deposit_${this.usdceToken.symbol}`;
86
+ return `extended_deposit_${this.usdcToken.symbol}`;
87
87
  }
88
88
  //abstract means the method has no implementation in this class; instead, child classes must implement it.
89
89
  protected async getAPY(
@@ -173,7 +173,7 @@ export class ExtendedAdapter extends BaseAdapter<
173
173
  }[] {
174
174
  return [
175
175
  {
176
- target: this.usdceToken.address,
176
+ target: this.usdcToken.address,
177
177
  method: "approve",
178
178
  packedArguments: [this.config.extendedContract.toBigInt()],
179
179
  id: this._depositApproveProofReadableId(),
@@ -204,14 +204,14 @@ export class ExtendedAdapter extends BaseAdapter<
204
204
 
205
205
  async getDepositCall(params: DepositParams): Promise<ManageCall[]> {
206
206
  try {
207
- const salt = Math.floor(Math.random() * 10 ** this.usdceToken.decimals);
207
+ const salt = Math.floor(Math.random() * 10 ** this.usdcToken.decimals);
208
208
  const amount = uint256.bnToUint256(params.amount.toWei());
209
209
  return [
210
210
  {
211
211
  proofReadableId: this._depositApproveProofReadableId(),
212
212
  sanitizer: SIMPLE_SANITIZER,
213
213
  call: {
214
- contractAddress: this.usdceToken.address,
214
+ contractAddress: this.usdcToken.address,
215
215
  selector: hash.getSelectorFromName("approve"),
216
216
  calldata: [
217
217
  this.config.extendedContract.toBigInt(),
@@ -7,4 +7,5 @@ export * from "./vesu-modify-position-adapter";
7
7
  export * from "./extended-adapter";
8
8
  export * from "./adapter-utils";
9
9
  export * from "./token-transfer-adapter";
10
- export * from "./avnu-adapter";
10
+ export * from "./avnu-adapter";
11
+ export * from "./svk-troves-adapter";
@@ -0,0 +1,364 @@
1
+ import { ContractAddr, Web3Number } from "@/dataTypes";
2
+ import { Protocols } from "@/interfaces";
3
+ import {
4
+ BaseAdapter,
5
+ BaseAdapterConfig,
6
+ SupportedPosition,
7
+ PositionInfo,
8
+ PositionAPY,
9
+ APYType,
10
+ ManageCall,
11
+ AdapterLeafType,
12
+ GenerateCallFn,
13
+ DepositParams,
14
+ WithdrawParams,
15
+ PositionAmount,
16
+ } from "./baseAdapter";
17
+ import { SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
18
+ import { hash, uint256, Contract } from "starknet";
19
+ import { logger } from "@/utils";
20
+ // Troves SVK universal vault ABI: ERC-4626 + SVK views (e.g. due_assets_from_owner).
21
+ import universalVaultAbi from "@/data/universal-vault.abi.json";
22
+
23
+ /** Public Troves strategies feed (APY + metadata). Override in config for tests. */
24
+ export const DEFAULT_TROVES_STRATEGIES_API = "https://app.troves.fi/api/strategies";
25
+
26
+ export interface SvkTrovesAdapterConfig extends BaseAdapterConfig {
27
+ /**
28
+ * On-chain Troves / SVK strategy vault: ERC-4626-style `deposit` / `withdraw` on the underlying asset,
29
+ * plus `due_assets_from_owner` for redemption NFT value still owed to an owner.
30
+ */
31
+ strategyVault: ContractAddr;
32
+ /**
33
+ * Troves API `strategies[].id` string (e.g. `hyper_xstrk`). Used to resolve APY from the public JSON feed.
34
+ */
35
+ trovesStrategyId: string;
36
+ /**
37
+ * Address whose vault **share** balance and **pending** redemption assets are measured.
38
+ * Defaults to `vaultAllocator` when omitted (typical STRKFarm vault wiring).
39
+ */
40
+ positionOwner?: ContractAddr;
41
+ /** Optional APY endpoint (defaults to {@link DEFAULT_TROVES_STRATEGIES_API}). */
42
+ trovesStrategiesApiUrl?: string;
43
+ }
44
+
45
+ type TrovesStrategiesApiPayload = {
46
+ strategies?: Array<{
47
+ id: string;
48
+ apy?: number | string;
49
+ }>;
50
+ };
51
+
52
+ function parseTrovesApyField(raw: unknown): number {
53
+ if (typeof raw === "number" && Number.isFinite(raw)) {
54
+ return raw;
55
+ }
56
+ if (typeof raw === "string") {
57
+ const n = Number.parseFloat(raw);
58
+ if (Number.isFinite(n)) {
59
+ return n;
60
+ }
61
+ }
62
+ return 0;
63
+ }
64
+
65
+ /**
66
+ * Universal adapter for **Starknet Vault Kit (SVK)** style Troves strategies:
67
+ * approve underlying → `deposit(assets, receiver)`, and `withdraw(assets, receiver, owner)` on the strategy vault.
68
+ *
69
+ * **Position sizing** (underlying asset, same decimals as `baseToken`):
70
+ * - Liquid: `convert_to_assets(balance_of(positionOwner))` on the strategy vault (vault shares).
71
+ * - Pending redemptions: `due_assets_from_owner(positionOwner)` on the same vault (NFT / queue claims not yet settled).
72
+ *
73
+ * **APY**: Fetched from Troves public API by `trovesStrategyId` (numeric `apy` field; non-numeric marketing values fall back to `0`).
74
+ */
75
+ export class SvkTrovesAdapter extends BaseAdapter<DepositParams, WithdrawParams> {
76
+ readonly config: SvkTrovesAdapterConfig;
77
+
78
+ constructor(config: SvkTrovesAdapterConfig) {
79
+ super(config, SvkTrovesAdapter.name, Protocols.TROVES);
80
+ this.config = config;
81
+ }
82
+
83
+ /** Owner used for share balance + `due_assets_from_owner`. */
84
+ private _positionOwner(): ContractAddr {
85
+ return this.config.positionOwner ?? this.config.vaultAllocator;
86
+ }
87
+
88
+ /**
89
+ * Proof readable IDs must stay ≤ 31 chars (Cairo short string). We derive a short ASCII suffix from
90
+ * `strategyVault` address so multiple SVK adapters in one tree stay distinct.
91
+ */
92
+ private _proofSuffix(): string {
93
+ return this.config.strategyVault.address.replace(/^0x/, "").slice(-6);
94
+ }
95
+
96
+ private _depositApproveProofReadableId(): string {
97
+ return `appr_dep_svk_${this._proofSuffix()}`;
98
+ }
99
+
100
+ private _depositCallProofReadableId(): string {
101
+ return `dep_svk_${this._proofSuffix()}`;
102
+ }
103
+
104
+ private _withdrawCallProofReadableId(): string {
105
+ return `wtdrw_svk_${this._proofSuffix()}`;
106
+ }
107
+
108
+ protected async getAPY(supportedPosition: SupportedPosition): Promise<PositionAPY> {
109
+ const CACHE_KEY = `svk_apy_${this.config.trovesStrategyId}`;
110
+ const cached = this.getCache<PositionAPY>(CACHE_KEY);
111
+ if (cached) {
112
+ return cached;
113
+ }
114
+
115
+ const url = this.config.trovesStrategiesApiUrl ?? DEFAULT_TROVES_STRATEGIES_API;
116
+
117
+ try {
118
+ const res = await fetch(url);
119
+ if (!res.ok) {
120
+ logger.warn(`${SvkTrovesAdapter.name}::getAPY: HTTP ${res.status} from ${url}`);
121
+ const fallback = { apy: 0, type: APYType.BASE };
122
+ this.setCache(CACHE_KEY, fallback, 300_000);
123
+ return fallback;
124
+ }
125
+ const body = (await res.json()) as TrovesStrategiesApiPayload;
126
+ const row = body.strategies?.find((s) => s.id === this.config.trovesStrategyId);
127
+ if (!row) {
128
+ logger.warn(
129
+ `${SvkTrovesAdapter.name}::getAPY: strategy id not found: ${this.config.trovesStrategyId}`,
130
+ );
131
+ const fallback = { apy: 0, type: APYType.BASE };
132
+ this.setCache(CACHE_KEY, fallback, 300_000);
133
+ return fallback;
134
+ }
135
+ const apy = parseTrovesApyField(row.apy);
136
+ const result: PositionAPY = { apy, type: APYType.BASE };
137
+ this.setCache(CACHE_KEY, result, 300_000);
138
+ return result;
139
+ } catch (error) {
140
+ logger.error(`${SvkTrovesAdapter.name}::getAPY:`, error);
141
+ throw error;
142
+ }
143
+ }
144
+
145
+ protected async getPosition(supportedPosition: SupportedPosition): Promise<PositionAmount | null> {
146
+ const CACHE_KEY = `svk_pos_${this.config.strategyVault.address}_${this._positionOwner().address}`;
147
+ const cached = this.getCache<PositionAmount>(CACHE_KEY);
148
+ if (cached) {
149
+ return cached;
150
+ }
151
+
152
+ try {
153
+ const vault = new Contract({
154
+ abi: universalVaultAbi as never,
155
+ address: this.config.strategyVault.address,
156
+ providerOrAccount: this.config.networkConfig.provider,
157
+ });
158
+
159
+ const owner = this._positionOwner();
160
+ const decimals = supportedPosition.asset.decimals;
161
+ const shares = await vault.balance_of(owner.address);
162
+ const shareU256 = uint256.bnToUint256(shares);
163
+ const liquidAssetsRaw: any = await vault.convert_to_assets(shareU256);
164
+ const liquid = Web3Number.fromWei(liquidAssetsRaw.toString(), decimals);
165
+
166
+ let pending = Web3Number.fromWei("0", decimals);
167
+ try {
168
+ const dueRaw: any = await vault.call("due_assets_from_owner", [owner.address]);
169
+ pending = Web3Number.fromWei(dueRaw.toString(), decimals);
170
+ } catch (e) {
171
+ logger.warn(
172
+ `${SvkTrovesAdapter.name}::getPosition: due_assets_from_owner failed (treating as 0): ${(e as Error).message}`,
173
+ );
174
+ }
175
+
176
+ const total = liquid.plus(pending);
177
+ const remarks = `Troves ${this.config.trovesStrategyId} holdings`
178
+
179
+ const result: PositionAmount = {
180
+ amount: total,
181
+ remarks,
182
+ };
183
+
184
+ this.setCache(CACHE_KEY, result, 60_000);
185
+ return result;
186
+ } catch (error) {
187
+ logger.error(`${SvkTrovesAdapter.name}::getPosition:`, error);
188
+ throw error;
189
+ }
190
+ }
191
+
192
+ async maxDeposit(amount?: Web3Number): Promise<PositionInfo> {
193
+ const baseToken = this.config.baseToken;
194
+ if (!amount) {
195
+ return {
196
+ tokenInfo: baseToken,
197
+ amount: new Web3Number("999999999999999999999999999", baseToken.decimals),
198
+ usdValue: 999999999999999999999999999,
199
+ remarks: "Max deposit (unbounded placeholder)",
200
+ apy: await this.getAPY({ asset: baseToken, isDebt: false }),
201
+ protocol: this.protocol,
202
+ };
203
+ }
204
+ const usdValue = await this.getUSDValue(baseToken, amount);
205
+ return {
206
+ tokenInfo: baseToken,
207
+ amount,
208
+ usdValue,
209
+ remarks: "Deposit amount",
210
+ apy: await this.getAPY({ asset: baseToken, isDebt: false }),
211
+ protocol: this.protocol,
212
+ };
213
+ }
214
+
215
+ async maxWithdraw(): Promise<PositionInfo> {
216
+ const baseToken = this.config.baseToken;
217
+ const current = await this.getPosition({ asset: baseToken, isDebt: false });
218
+ const pos = current ?? { amount: new Web3Number("0", baseToken.decimals), remarks: "" };
219
+ const usdValue = await this.getUSDValue(baseToken, pos.amount);
220
+ return {
221
+ tokenInfo: baseToken,
222
+ amount: pos.amount,
223
+ usdValue,
224
+ remarks: "Max withdraw (liquid + pending redemption, underlying units)",
225
+ apy: await this.getAPY({ asset: baseToken, isDebt: false }),
226
+ protocol: this.protocol,
227
+ };
228
+ }
229
+
230
+ protected _getDepositLeaf(): {
231
+ target: ContractAddr;
232
+ method: string;
233
+ packedArguments: bigint[];
234
+ sanitizer: ContractAddr;
235
+ id: string;
236
+ }[] {
237
+ const baseToken = this.config.baseToken;
238
+ const strategyVault = this.config.strategyVault;
239
+ const receiver = this.config.vaultAllocator;
240
+
241
+ return [
242
+ {
243
+ target: baseToken.address,
244
+ method: "approve",
245
+ packedArguments: [strategyVault.toBigInt()],
246
+ sanitizer: SIMPLE_SANITIZER,
247
+ id: this._depositApproveProofReadableId(),
248
+ },
249
+ {
250
+ target: strategyVault,
251
+ method: "deposit",
252
+ packedArguments: [receiver.toBigInt()],
253
+ sanitizer: SIMPLE_SANITIZER,
254
+ id: this._depositCallProofReadableId(),
255
+ },
256
+ ];
257
+ }
258
+
259
+ protected _getWithdrawLeaf(): {
260
+ target: ContractAddr;
261
+ method: string;
262
+ packedArguments: bigint[];
263
+ sanitizer: ContractAddr;
264
+ id: string;
265
+ }[] {
266
+ const strategyVault = this.config.strategyVault;
267
+ const recv = this.config.vaultAllocator;
268
+ const owner = this.config.vaultAllocator;
269
+
270
+ return [
271
+ {
272
+ target: strategyVault,
273
+ method: "withdraw",
274
+ packedArguments: [recv.toBigInt(), owner.toBigInt()],
275
+ sanitizer: SIMPLE_SANITIZER,
276
+ id: this._withdrawCallProofReadableId(),
277
+ },
278
+ ];
279
+ }
280
+
281
+ getDepositAdapter(): AdapterLeafType<DepositParams> {
282
+ const leafConfigs = this._getDepositLeaf();
283
+ const leaves = leafConfigs.map((config) => {
284
+ const { target, method, packedArguments, sanitizer, id } = config;
285
+ return this.constructSimpleLeafData({ id, target, method, packedArguments }, sanitizer);
286
+ });
287
+ return { leaves, callConstructor: this.getDepositCall.bind(this) as unknown as GenerateCallFn<DepositParams> };
288
+ }
289
+
290
+ getWithdrawAdapter(): AdapterLeafType<WithdrawParams> {
291
+ const leafConfigs = this._getWithdrawLeaf();
292
+ const leaves = leafConfigs.map((config) => {
293
+ const { target, method, packedArguments, sanitizer, id } = config;
294
+ return this.constructSimpleLeafData({ id, target, method, packedArguments }, sanitizer);
295
+ });
296
+ return { leaves, callConstructor: this.getWithdrawCall.bind(this) as unknown as GenerateCallFn<WithdrawParams> };
297
+ }
298
+
299
+ async getDepositCall(params: DepositParams): Promise<ManageCall[]> {
300
+ const baseToken = this.config.baseToken;
301
+ const strategyVault = this.config.strategyVault;
302
+ const amount = params.amount;
303
+ const uint256Amount = uint256.bnToUint256(amount.toWei());
304
+ const receiver = this.config.vaultAllocator;
305
+
306
+ return [
307
+ {
308
+ proofReadableId: this._depositApproveProofReadableId(),
309
+ sanitizer: SIMPLE_SANITIZER,
310
+ call: {
311
+ contractAddress: baseToken.address,
312
+ selector: hash.getSelectorFromName("approve"),
313
+ calldata: [
314
+ strategyVault.toBigInt(),
315
+ toBigInt(uint256Amount.low.toString()),
316
+ toBigInt(uint256Amount.high.toString()),
317
+ ],
318
+ },
319
+ },
320
+ {
321
+ proofReadableId: this._depositCallProofReadableId(),
322
+ sanitizer: SIMPLE_SANITIZER,
323
+ call: {
324
+ contractAddress: strategyVault,
325
+ selector: hash.getSelectorFromName("deposit"),
326
+ calldata: [
327
+ toBigInt(uint256Amount.low.toString()),
328
+ toBigInt(uint256Amount.high.toString()),
329
+ receiver.toBigInt(),
330
+ ],
331
+ },
332
+ },
333
+ ];
334
+ }
335
+
336
+ async getWithdrawCall(params: WithdrawParams): Promise<ManageCall[]> {
337
+ const strategyVault = this.config.strategyVault;
338
+ const amount = params.amount;
339
+ const uint256Amount = uint256.bnToUint256(amount.toWei());
340
+ const recv = this.config.vaultAllocator;
341
+ const owner = this.config.vaultAllocator;
342
+
343
+ return [
344
+ {
345
+ proofReadableId: this._withdrawCallProofReadableId(),
346
+ sanitizer: SIMPLE_SANITIZER,
347
+ call: {
348
+ contractAddress: strategyVault,
349
+ selector: hash.getSelectorFromName("withdraw"),
350
+ calldata: [
351
+ toBigInt(uint256Amount.low.toString()),
352
+ toBigInt(uint256Amount.high.toString()),
353
+ recv.toBigInt(),
354
+ owner.toBigInt(),
355
+ ],
356
+ },
357
+ },
358
+ ];
359
+ }
360
+
361
+ getHealthFactor(): Promise<number> {
362
+ return Promise.resolve(10);
363
+ }
364
+ }