@strkfarm/sdk 2.0.0-dev.3 → 2.0.0-dev.31

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.
Files changed (75) hide show
  1. package/dist/cli.js +190 -36
  2. package/dist/cli.mjs +188 -34
  3. package/dist/index.browser.global.js +78475 -45620
  4. package/dist/index.browser.mjs +19580 -9901
  5. package/dist/index.d.ts +3763 -1424
  6. package/dist/index.js +20977 -11063
  7. package/dist/index.mjs +20945 -11087
  8. package/package.json +1 -1
  9. package/src/data/avnu.abi.json +840 -0
  10. package/src/data/ekubo-price-fethcer.abi.json +265 -0
  11. package/src/dataTypes/_bignumber.ts +13 -4
  12. package/src/dataTypes/bignumber.browser.ts +6 -1
  13. package/src/dataTypes/bignumber.node.ts +5 -1
  14. package/src/dataTypes/index.ts +3 -2
  15. package/src/dataTypes/mynumber.ts +141 -0
  16. package/src/global.ts +76 -41
  17. package/src/index.browser.ts +2 -1
  18. package/src/interfaces/common.tsx +175 -3
  19. package/src/modules/ExtendedWrapperSDk/types.ts +28 -5
  20. package/src/modules/ExtendedWrapperSDk/wrapper.ts +275 -59
  21. package/src/modules/apollo-client-config.ts +28 -0
  22. package/src/modules/avnu.ts +4 -4
  23. package/src/modules/ekubo-pricer.ts +79 -0
  24. package/src/modules/ekubo-quoter.ts +48 -30
  25. package/src/modules/erc20.ts +17 -0
  26. package/src/modules/harvests.ts +43 -29
  27. package/src/modules/pragma.ts +23 -8
  28. package/src/modules/pricer-from-api.ts +156 -15
  29. package/src/modules/pricer-lst.ts +1 -1
  30. package/src/modules/pricer.ts +40 -4
  31. package/src/modules/pricerBase.ts +2 -1
  32. package/src/node/deployer.ts +36 -1
  33. package/src/node/pricer-redis.ts +2 -1
  34. package/src/strategies/base-strategy.ts +78 -10
  35. package/src/strategies/ekubo-cl-vault.tsx +906 -347
  36. package/src/strategies/factory.ts +159 -0
  37. package/src/strategies/index.ts +7 -1
  38. package/src/strategies/registry.ts +239 -0
  39. package/src/strategies/sensei.ts +335 -7
  40. package/src/strategies/svk-strategy.ts +97 -27
  41. package/src/strategies/types.ts +4 -0
  42. package/src/strategies/universal-adapters/adapter-utils.ts +2 -1
  43. package/src/strategies/universal-adapters/avnu-adapter.ts +180 -265
  44. package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
  45. package/src/strategies/universal-adapters/common-adapter.ts +206 -203
  46. package/src/strategies/universal-adapters/extended-adapter.ts +490 -316
  47. package/src/strategies/universal-adapters/index.ts +11 -8
  48. package/src/strategies/universal-adapters/svk-troves-adapter.ts +364 -0
  49. package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
  50. package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +200 -0
  51. package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
  52. package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +476 -0
  53. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1067 -704
  54. package/src/strategies/universal-adapters/vesu-position-common.ts +251 -0
  55. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
  56. package/src/strategies/universal-lst-muliplier-strategy.tsx +397 -204
  57. package/src/strategies/universal-strategy.tsx +1426 -1173
  58. package/src/strategies/vesu-extended-strategy/services/executionService.ts +2233 -0
  59. package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +4087 -0
  60. package/src/strategies/vesu-extended-strategy/services/ltv-imbalance-rebalance-math.ts +783 -0
  61. package/src/strategies/vesu-extended-strategy/services/operationService.ts +38 -16
  62. package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +88 -0
  63. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +1 -0
  64. package/src/strategies/vesu-extended-strategy/utils/constants.ts +5 -6
  65. package/src/strategies/vesu-extended-strategy/utils/helper.ts +259 -103
  66. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +688 -817
  67. package/src/strategies/vesu-rebalance.tsx +255 -152
  68. package/src/utils/cacheClass.ts +11 -2
  69. package/src/utils/health-factor-math.ts +4 -1
  70. package/src/utils/index.ts +3 -1
  71. package/src/utils/logger.browser.ts +22 -4
  72. package/src/utils/logger.node.ts +259 -24
  73. package/src/utils/starknet-call-parser.ts +1036 -0
  74. package/src/utils/strategy-utils.ts +61 -0
  75. package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
@@ -1,4 +1,9 @@
1
- import { BaseAdapter, DepositParams, WithdrawParams, BaseAdapterConfig } from "./baseAdapter";
1
+ import {
2
+ BaseAdapter,
3
+ DepositParams,
4
+ WithdrawParams,
5
+ BaseAdapterConfig,
6
+ } from "./baseAdapter";
2
7
  import { Protocols } from "@/interfaces";
3
8
  import { SupportedPosition } from "./baseAdapter";
4
9
  import { PositionAPY, APYType, PositionAmount } from "./baseAdapter";
@@ -9,9 +14,13 @@ import { ContractAddr } from "@/dataTypes";
9
14
  import { AVNU_EXCHANGE_FOR_LEGACY_USDC } from "./adapter-utils";
10
15
  import { StandardMerkleTree } from "@/utils";
11
16
  import { hash, uint256 } from "starknet";
12
- import { Global } from "@/global";
13
17
  import { AdapterLeafType, GenerateCallFn } from "./baseAdapter";
14
- import { AVNU_LEGACY_SANITIZER, EXTENDED_SANITIZER, SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
18
+ import {
19
+ AVNU_LEGACY_SANITIZER,
20
+ EXTENDED_SANITIZER,
21
+ SIMPLE_SANITIZER,
22
+ toBigInt,
23
+ } from "./adapter-utils";
15
24
  import ExtendedWrapper, {
16
25
  AssetOperationStatus,
17
26
  AssetOperationType,
@@ -24,17 +33,21 @@ import { Balance, OpenOrder, OrderStatus } from "@/modules/ExtendedWrapperSDk";
24
33
  import axios from "axios";
25
34
  import { AvnuAdapter } from "./avnu-adapter";
26
35
  import { logger } from "@/utils";
36
+ import { TokenInfo } from "@/interfaces";
37
+
27
38
  export interface ExtendedAdapterConfig extends BaseAdapterConfig {
28
39
  vaultIdExtended: number;
29
40
  extendedContract: ContractAddr; //0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f
30
- extendedBackendUrl: string;
31
- extendedApiKey: string;
41
+ extendedBackendReadUrl: string;
42
+ extendedBackendWriteUrl: string;
32
43
  extendedTimeout: number;
33
44
  extendedRetries: number;
34
45
  extendedBaseUrl: string;
35
46
  extendedMarketName: string;
36
47
  extendedPrecision: number;
37
48
  avnuAdapter: AvnuAdapter;
49
+ retryDelayForOrderStatus: number;
50
+ minimumExtendedMovementAmount: number;
38
51
  }
39
52
 
40
53
  export class ExtendedAdapter extends BaseAdapter<
@@ -43,40 +56,80 @@ export class ExtendedAdapter extends BaseAdapter<
43
56
  > {
44
57
  readonly config: ExtendedAdapterConfig;
45
58
  readonly client: ExtendedWrapper;
59
+ readonly usdcToken: TokenInfo;
60
+ readonly retryDelayForOrderStatus: number;
61
+ readonly minimumExtendedMovementAmount: number;
46
62
 
47
63
  constructor(config: ExtendedAdapterConfig) {
48
64
  super(config, ExtendedAdapter.name, Protocols.EXTENDED);
49
65
  this.config = config as ExtendedAdapterConfig;
50
66
  const client = new ExtendedWrapper({
51
- baseUrl: this.config.extendedBackendUrl,
52
- apiKey: this.config.extendedApiKey,
67
+ writeUrl: this.config.extendedBackendWriteUrl,
68
+ readUrl: this.config.extendedBackendReadUrl,
69
+ apiKey: "", // todo add auth
53
70
  timeout: this.config.extendedTimeout,
54
71
  retries: this.config.extendedRetries,
55
72
  });
73
+ this.minimumExtendedMovementAmount =
74
+ this.config.minimumExtendedMovementAmount ?? 5; //5 usdc
56
75
  this.client = client;
76
+ this.retryDelayForOrderStatus =
77
+ this.config.retryDelayForOrderStatus ?? 3000;
78
+ this.usdcToken = this.config.supportedPositions[0].asset;
79
+ }
80
+
81
+ private _depositApproveProofReadableId(): string {
82
+ return `extended_approve_${this.usdcToken.symbol}`;
83
+ }
84
+
85
+ private _depositCallProofReadableId(): string {
86
+ return `extended_deposit_${this.usdcToken.symbol}`;
57
87
  }
58
88
  //abstract means the method has no implementation in this class; instead, child classes must implement it.
59
89
  protected async getAPY(
60
- supportedPosition: SupportedPosition
90
+ supportedPosition: SupportedPosition,
61
91
  ): Promise<PositionAPY> {
62
92
  /** Considering supportedPosiiton.isDebt as side parameter to get the funding rates */
63
93
  const side = supportedPosition.isDebt ? "LONG" : "SHORT";
64
- const fundingRates = await this.client.getFundingRates(this.config.extendedMarketName, side);
65
- if (fundingRates.status !== "OK") {
94
+ const fundingRates = await this.getFundingRates(side);
95
+ if (!fundingRates.success) {
66
96
  logger.error("error getting funding rates", fundingRates);
67
97
  return { apy: 0, type: APYType.BASE };
68
98
  }
69
- const fundingRate:FundingRate = fundingRates.data[0];
99
+ const fundingRate: FundingRate = fundingRates.data[0];
70
100
  const apy = Number(fundingRate.f) * 365 * 24;
71
101
  return { apy: apy, type: APYType.BASE };
72
102
  }
73
103
 
104
+ public async getFundingRates(
105
+ side: string,
106
+ startTime?: number,
107
+ endTime?: number,
108
+ ): Promise<{ success: boolean; data: FundingRate[] }> {
109
+ try {
110
+ const response = await this.client.getFundingRates(
111
+ this.config.extendedMarketName,
112
+ side,
113
+ startTime,
114
+ endTime,
115
+ );
116
+ if (response.status !== "OK") {
117
+ logger.error("error getting funding rates", response.data);
118
+ return { success: false, data: [] };
119
+ }
120
+ logger.debug(
121
+ `success getting funding rates market=${this.config.extendedMarketName} side=${side} count=${response.data.length}`,
122
+ );
123
+ return { success: true, data: response.data };
124
+ } catch (err) {
125
+ logger.error("error getting funding rates", err);
126
+ return { success: false, data: [] };
127
+ }
128
+ }
129
+
74
130
  protected async getPosition(
75
- supportedPosition: SupportedPosition
131
+ supportedPosition: SupportedPosition,
76
132
  ): Promise<PositionAmount> {
77
- if (!this.client) {
78
- throw new Error("Client not initialized");
79
- }
80
133
  const holdings = await this.getExtendedDepositAmount();
81
134
  if (!holdings) {
82
135
  throw new Error("No position found");
@@ -85,7 +138,7 @@ export class ExtendedAdapter extends BaseAdapter<
85
138
  const amount = holdings.equity;
86
139
  return Promise.resolve({
87
140
  amount: new Web3Number(amount, 0),
88
- remarks: `${holdings.availableForWithdrawal} ${holdings.equity}`,
141
+ remarks: `Extended Equity`,
89
142
  });
90
143
  }
91
144
 
@@ -118,29 +171,12 @@ export class ExtendedAdapter extends BaseAdapter<
118
171
  sanitizer: ContractAddr;
119
172
  id: string;
120
173
  }[] {
121
- const usdceToken = Global.getDefaultTokens().find(token => token.symbol === "USDCe");
122
174
  return [
123
175
  {
124
- target: this.config.supportedPositions[0].asset.address,
125
- method: "approve",
126
- packedArguments: [
127
- AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
128
- ],
129
- id: `extended_approve_${this.config.supportedPositions[0].asset.symbol}`,
130
- sanitizer: AVNU_LEGACY_SANITIZER,
131
- },
132
- {
133
- target: AVNU_EXCHANGE_FOR_LEGACY_USDC,
134
- method: "swap_to_legacy",
135
- packedArguments: [],
136
- id: `extended_swap_to_legacy_${this.config.supportedPositions[0].asset.symbol}`,
137
- sanitizer: AVNU_LEGACY_SANITIZER,
138
- },
139
- {
140
- target: usdceToken!.address,
176
+ target: this.usdcToken.address,
141
177
  method: "approve",
142
178
  packedArguments: [this.config.extendedContract.toBigInt()],
143
- id: `extended_approve_${usdceToken!.symbol}`,
179
+ id: this._depositApproveProofReadableId(),
144
180
  sanitizer: SIMPLE_SANITIZER,
145
181
  },
146
182
  {
@@ -148,55 +184,11 @@ export class ExtendedAdapter extends BaseAdapter<
148
184
  method: "deposit",
149
185
  packedArguments: [BigInt(this.config.vaultIdExtended)],
150
186
  sanitizer: EXTENDED_SANITIZER,
151
- id: `extended_deposit_${usdceToken!.symbol}`,
187
+ id: this._depositCallProofReadableId(),
152
188
  },
153
189
  ];
154
190
  }
155
191
 
156
- getSwapFromLegacyLeaf(): AdapterLeafType<DepositParams> {
157
- const leafConfigs = this._getSwapFromLegacyLeaf();
158
- const leaves = leafConfigs.map(config => {
159
- const { target, method, packedArguments, sanitizer, id } = config;
160
- const leaf = this.constructSimpleLeafData({
161
- id: id,
162
- target,
163
- method,
164
- packedArguments
165
- }, sanitizer);
166
- return leaf;
167
- });
168
- return { leaves, callConstructor: this.getSwapFromLegacyCall.bind(this) as unknown as GenerateCallFn<DepositParams> };
169
- }
170
-
171
- protected _getSwapFromLegacyLeaf(): {
172
- target: ContractAddr;
173
- method: string;
174
- packedArguments: bigint[];
175
- sanitizer: ContractAddr;
176
- id: string;
177
- }[] {
178
- const usdceToken = Global.getDefaultTokens().find(token => token.symbol === "USDCe");
179
- return [
180
- {
181
- target: usdceToken!.address,
182
- method: "approve",
183
- packedArguments: [
184
- AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
185
- ],
186
- id: `extendedswaplegacyapprove_${usdceToken!.symbol}`,
187
- sanitizer: AVNU_LEGACY_SANITIZER,
188
- },
189
- {
190
- target: AVNU_EXCHANGE_FOR_LEGACY_USDC,
191
- method: "swap_to_new",
192
- packedArguments: [],
193
- id: `extended_swap_to_new_${usdceToken!.symbol}`,
194
- sanitizer: AVNU_LEGACY_SANITIZER,
195
- },
196
- ]
197
- }
198
-
199
-
200
192
  protected _getWithdrawLeaf(): {
201
193
  target: ContractAddr;
202
194
  method: string;
@@ -212,54 +204,14 @@ export class ExtendedAdapter extends BaseAdapter<
212
204
 
213
205
  async getDepositCall(params: DepositParams): Promise<ManageCall[]> {
214
206
  try {
215
- const usdcToken = this.config.supportedPositions[0].asset;
216
- const usdceToken = Global.getDefaultTokens().find(token => token.symbol === "USDCe");
217
- const salt = Math.floor(
218
- Math.random() * 10 ** usdcToken.decimals
219
- );
220
- // Give approval for more amount than the required amount
221
- const amount = uint256.bnToUint256(params.amount.multipliedBy(10).toWei());
222
- const quotes = await this.config.avnuAdapter.getQuotesAvnu(
223
- usdcToken.address.toString(),
224
- usdceToken!.address.toString(),
225
- params.amount.toNumber(),
226
- this.config.avnuAdapter.config.vaultAllocator.address.toString(),
227
- usdceToken!.decimals,
228
- false
229
- )
230
-
231
- if (!quotes) {
232
- logger.error("error getting quotes from avnu");
233
- return [];
234
- }
235
- const getCalldata = await this.config.avnuAdapter.getSwapCallData(quotes!);
236
- const swapCallData = getCalldata[0];
237
- //change extended sanitizer here
207
+ const salt = Math.floor(Math.random() * 10 ** this.usdcToken.decimals);
208
+ const amount = uint256.bnToUint256(params.amount.toWei());
238
209
  return [
239
210
  {
240
- sanitizer: AVNU_LEGACY_SANITIZER,
241
- call: {
242
- contractAddress: usdcToken.address,
243
- selector: hash.getSelectorFromName("approve"),
244
- calldata: [
245
- AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
246
- toBigInt(amount.low.toString()), // amount low
247
- toBigInt(amount.high.toString()), // amount high
248
- ],
249
- },
250
- },
251
- {
252
- sanitizer: AVNU_LEGACY_SANITIZER,
253
- call: {
254
- contractAddress: AVNU_EXCHANGE_FOR_LEGACY_USDC,
255
- selector: hash.getSelectorFromName("swap_to_legacy"),
256
- calldata: swapCallData,
257
- },
258
- },
259
- {
211
+ proofReadableId: this._depositApproveProofReadableId(),
260
212
  sanitizer: SIMPLE_SANITIZER,
261
213
  call: {
262
- contractAddress: usdceToken!.address,
214
+ contractAddress: this.usdcToken.address,
263
215
  selector: hash.getSelectorFromName("approve"),
264
216
  calldata: [
265
217
  this.config.extendedContract.toBigInt(),
@@ -269,6 +221,7 @@ export class ExtendedAdapter extends BaseAdapter<
269
221
  },
270
222
  },
271
223
  {
224
+ proofReadableId: this._depositCallProofReadableId(),
272
225
  sanitizer: EXTENDED_SANITIZER,
273
226
  call: {
274
227
  contractAddress: this.config.extendedContract,
@@ -287,84 +240,8 @@ export class ExtendedAdapter extends BaseAdapter<
287
240
  }
288
241
  }
289
242
 
290
- getProofsForFromLegacySwap<T>(tree: StandardMerkleTree): { proofs: string[][], callConstructor: GenerateCallFn<DepositParams> | GenerateCallFn<WithdrawParams> } {
291
- let proofGroups: string[][] = [];
292
-
293
- const ids = this.getSwapFromLegacyLeaf().leaves.map(l => l.readableId)
294
- // console.log(`${this.name}::getProofs ids: ${ids}`);
295
- for (const [i, v] of tree.entries()) {
296
- // console.log(`${this.name}::getProofs v: ${v.readableId}`);
297
- if (ids.includes(v.readableId)) {
298
- //console.log(`${this.name}::getProofs found id: ${v.readableId}`);
299
- proofGroups.push(tree.getProof(i));
300
- }
301
- }
302
- if (proofGroups.length != ids.length) {
303
- throw new Error(`Not all proofs found for IDs: ${ids.join(', ')}`);
304
- }
305
-
306
- // find leaf adapter
307
- return {
308
- proofs: proofGroups,
309
- callConstructor: this.getSwapFromLegacyCall.bind(this)
310
- };
311
- }
312
-
313
- async getSwapFromLegacyCall(params: DepositParams): Promise<ManageCall[]> {
314
- try {
315
- const usdcToken = this.config.supportedPositions[0].asset;
316
- const usdceToken = Global.getDefaultTokens().find(token => token.symbol === "USDCe");
317
- // Give approval for more amount than the required amount
318
- const amount = uint256.bnToUint256(params.amount.multipliedBy(10).toWei());
319
- const quotes = await this.config.avnuAdapter.getQuotesAvnu(
320
- usdceToken!.address.toString(),
321
- usdcToken!.address.toString(),
322
- params.amount.toNumber(),
323
- this.config.avnuAdapter.config.vaultAllocator.address.toString(),
324
- usdcToken!.decimals,
325
- false
326
- )
327
-
328
- if (!quotes) {
329
- logger.error("error getting quotes from avnu");
330
- return [];
331
- }
332
- const getCalldata = await this.config.avnuAdapter.getSwapCallData(quotes!);
333
- const swapCallData = getCalldata[0];
334
- //change extended sanitizer here
335
- return [
336
- {
337
- sanitizer: AVNU_LEGACY_SANITIZER,
338
- call: {
339
- contractAddress: usdceToken!.address,
340
- selector: hash.getSelectorFromName("approve"),
341
- calldata: [
342
- AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
343
- toBigInt(amount.low.toString()), // amount low
344
- toBigInt(amount.high.toString()), // amount high
345
- ],
346
- },
347
- },
348
- {
349
- sanitizer: AVNU_LEGACY_SANITIZER,
350
- call: {
351
- contractAddress: AVNU_EXCHANGE_FOR_LEGACY_USDC,
352
- selector: hash.getSelectorFromName("swap_to_new"),
353
- calldata: swapCallData,
354
- },
355
- }
356
- ];
357
- } catch (error) {
358
- logger.error(`Error creating Deposit Call: ${error}`);
359
- return [];
360
- }
361
- }
362
- //Swap wbtc to usdc
363
243
  async getWithdrawCall(params: WithdrawParams): Promise<ManageCall[]> {
364
244
  try {
365
- if (!this.client) {
366
- throw new Error("Client not initialized");
367
- }
368
245
  return [];
369
246
  } catch (error) {
370
247
  logger.error(`Error creating Withdraw Call: ${error}`);
@@ -372,20 +249,105 @@ export class ExtendedAdapter extends BaseAdapter<
372
249
  }
373
250
  }
374
251
 
375
- async withdrawFromExtended(amount: Web3Number): Promise<boolean> {
252
+ async withdrawFromExtended(amount: Web3Number): Promise<{
253
+ status: boolean;
254
+ receivedTxnHash: boolean;
255
+ }> {
376
256
  try {
377
257
  if (!this.client) {
378
- throw new Error("Client not initialized");
258
+ logger.error("Client not initialized");
259
+ return {
260
+ status: false,
261
+ receivedTxnHash: false,
262
+ };
263
+ }
264
+ if (amount.lessThanOrEqualTo(0)) {
265
+ logger.error(
266
+ `Invalid withdrawal amount: ${amount.toNumber()}. Amount must be positive.`,
267
+ );
268
+ return {
269
+ status: false,
270
+ receivedTxnHash: false,
271
+ };
272
+ }
273
+ if (amount.lessThanOrEqualTo(this.minimumExtendedMovementAmount)) {
274
+ logger.warn(
275
+ `Withdrawal amount ${amount.toNumber()} is below minimum Extended movement amount ${this.minimumExtendedMovementAmount}. Skipping withdrawal.`,
276
+ );
277
+ return {
278
+ status: false,
279
+ receivedTxnHash: false,
280
+ };
281
+ }
282
+ const holdings = await this.getExtendedDepositAmount();
283
+ if (!holdings) {
284
+ logger.error(
285
+ "Cannot get holdings - unable to validate withdrawal amount",
286
+ );
287
+ return {
288
+ status: false,
289
+ receivedTxnHash: false,
290
+ };
291
+ }
292
+
293
+ const availableForWithdrawal = parseFloat(
294
+ holdings.availableForWithdrawal,
295
+ );
296
+ if (
297
+ !Number.isFinite(availableForWithdrawal) ||
298
+ availableForWithdrawal < 0
299
+ ) {
300
+ logger.error(
301
+ `Invalid availableForWithdrawal: ${holdings.availableForWithdrawal}. Expected a finite, non-negative number.`,
302
+ );
303
+ return {
304
+ status: false,
305
+ receivedTxnHash: false,
306
+ };
307
+ }
308
+
309
+ const withdrawalAmount = amount.toNumber();
310
+ if (withdrawalAmount > availableForWithdrawal) {
311
+ logger.error(
312
+ `Withdrawal amount ${withdrawalAmount} exceeds available balance ${availableForWithdrawal}`,
313
+ );
314
+ return {
315
+ status: false,
316
+ receivedTxnHash: false,
317
+ };
379
318
  }
380
- const withdrawalRequest = await this.client.withdrawUSDC(amount.toFixed(2));
319
+
320
+ logger.info(
321
+ `Withdrawing ${withdrawalAmount} from Extended. Available balance: ${availableForWithdrawal}`,
322
+ );
323
+
324
+ const withdrawalRequest = await this.client.withdrawUSDC(
325
+ amount.toFixed(2),
326
+ );
327
+
381
328
  if (withdrawalRequest.status === "OK") {
382
- const withdrawalStatus = await this.getDepositOrWithdrawalStatus(withdrawalRequest.data, AssetOperationType.WITHDRAWAL);
383
- return withdrawalStatus;
329
+ const withdrawalStatus = await this.getDepositOrWithdrawalStatus(
330
+ withdrawalRequest.data,
331
+ AssetOperationType.WITHDRAWAL,
332
+ );
333
+ return {
334
+ status: true,
335
+ receivedTxnHash: withdrawalStatus,
336
+ };
384
337
  }
385
- return false;
338
+ logger.error(
339
+ `Withdrawal request failed with status: ${withdrawalRequest.status}`,
340
+ );
341
+ return {
342
+ status: false,
343
+ receivedTxnHash: false,
344
+ };
386
345
  } catch (error) {
387
346
  logger.error(`Error creating Withdraw Call: ${error}`);
388
- return false;
347
+ return {
348
+ status: false,
349
+ receivedTxnHash: false,
350
+ };
389
351
  }
390
352
  }
391
353
 
@@ -394,21 +356,37 @@ export class ExtendedAdapter extends BaseAdapter<
394
356
  }
395
357
 
396
358
  async getExtendedDepositAmount(): Promise<Balance | undefined> {
397
- if (this.client === null) {
398
- logger.error("error initializing client");
399
- return undefined;
400
- }
401
- const result = await this.client.getHoldings();
402
- if (!result) {
403
- logger.error(`error getting holdings: ${result}`);
404
- return undefined;
405
- }
406
- const holdings = result.data;
407
- if (!holdings) {
408
- logger.error(`error getting holdings: ${holdings}`);
409
- return undefined;
359
+ const failResult = {
360
+ collateral_name: "",
361
+ balance: "0",
362
+ equity: "0",
363
+ availableForTrade: "0",
364
+ availableForWithdrawal: "0",
365
+ unrealisedPnl: "0",
366
+ initialMargin: "0",
367
+ marginRatio: "0",
368
+ updatedTime: Date.now(),
369
+ };
370
+ const fail = (message: string) => {
371
+ logger.error(message);
372
+ return failResult;
373
+ };
374
+
375
+ try {
376
+ const result = await this.client.getHoldings();
377
+ if (result.status !== "OK") {
378
+ return fail("error getting holdings - API returned null/undefined");
379
+ }
380
+ const holdings = result.data;
381
+ if (!holdings) {
382
+ return fail(
383
+ "holdings data is null/undefined - treating as zero balance",
384
+ );
385
+ }
386
+ return holdings;
387
+ } catch (error) {
388
+ return fail(`error getting holdings - exception: ${error}`);
410
389
  }
411
- return holdings;
412
390
  }
413
391
 
414
392
  async setLeverage(leverage: string, marketName: string): Promise<boolean> {
@@ -427,64 +405,77 @@ export class ExtendedAdapter extends BaseAdapter<
427
405
  }
428
406
 
429
407
  async getAllOpenPositions(): Promise<Position[] | null> {
430
- if (this.client === null) {
431
- logger.error("error initializing client");
432
- return null;
433
- }
434
- const response = await this.client.getPositionsForMarket(
435
- this.config.extendedMarketName
436
- );
437
- if (response.status === "OK") {
438
- if (response.data.length === 0) {
439
- return [];
440
- } else {
441
- return response.data;
408
+ try {
409
+ const response = await this.client.getPositions(
410
+ this.config.extendedMarketName,
411
+ );
412
+ if (response.status === "OK") {
413
+ if (response.data.length === 0) {
414
+ return [];
415
+ } else {
416
+ return response.data;
417
+ }
442
418
  }
443
- }
444
- return null;
445
- }
446
-
447
- async getOrderHistory(marketName: string): Promise<OpenOrder[] | null> {
448
- if (this.client === null) {
449
- logger.error("error initializing client");
419
+ return null;
420
+ } catch (err) {
421
+ logger.error("error getting all open positions", err);
450
422
  return null;
451
423
  }
452
- const result = await this.client.getOrderHistory(marketName);
453
- return result.data;
454
424
  }
455
425
 
426
+ // async getOrderHistory(marketName: string): Promise<OpenOrder[] | null> {
427
+ // try {
428
+ // const result = await axios.get<ApiResponse<OpenOrder[]>>(`${this.config.extendedBackendReadUrl}/marketOrders/${marketName}`);
429
+ // if (!result.data.success) {
430
+ // return null;
431
+ // }
432
+ // return result.data.data;
433
+ // } catch (err) {
434
+ // logger.error("error getting order history", err);
435
+ // return null;
436
+ // }
437
+ // }
438
+
456
439
  async getOrderStatus(
457
440
  orderId: string,
458
- marketName: string
441
+ marketName: string,
459
442
  ): Promise<OpenOrder | null> {
460
- if (this.client === null) {
461
- logger.error("error initializing client");
462
- return null;
463
- }
464
- const orderhistory = await this.getOrderHistory(marketName);
443
+ try {
444
+ if (this.client === null) {
445
+ logger.error("error initializing client");
446
+ return null;
447
+ }
448
+ const orderhistoryResponse =
449
+ await this.client.getOrderHistory(marketName);
465
450
 
466
- if (!orderhistory || orderhistory.length === 0) {
467
- logger.error(`error getting order: ${orderId}`);
468
- return null;
469
- }
470
- const order = orderhistory
471
- .slice(0, 3)
472
- .find((order) => order.id.toString() === orderId);
473
- if (!order) {
474
- logger.error(`error getting order: ${order}`);
451
+ if (
452
+ orderhistoryResponse.status !== "OK" ||
453
+ orderhistoryResponse.data.length === 0
454
+ ) {
455
+ return null;
456
+ }
457
+ const orderhistory = orderhistoryResponse.data;
458
+ const order = orderhistory
459
+ .slice(0, 20)
460
+ .find((order) => order.id.toString() === orderId);
461
+ if (order) {
462
+ return order;
463
+ }
464
+ return null; // Order not found
465
+ } catch (error) {
466
+ logger.error(`error getting order status: ${error}`);
475
467
  return null;
476
468
  }
477
- return order;
478
469
  }
479
470
 
480
- async fetchOrderBookBTCUSDC(): Promise<{
471
+ async fetchOrderBookFromExtended(): Promise<{
481
472
  status: boolean;
482
473
  bid: Web3Number;
483
474
  ask: Web3Number;
484
475
  }> {
485
476
  try {
486
477
  const res = await axios.get(
487
- `${this.config.extendedBaseUrl}/api/v1/info/markets/${this.config.extendedMarketName}/orderbook`
478
+ `${this.config.extendedBaseUrl}/api/v1/info/markets/${this.config.extendedMarketName}/orderbook`,
488
479
  );
489
480
  let ask_price = new Web3Number(0, 0);
490
481
  let bid_price = new Web3Number(0, 0);
@@ -502,12 +493,12 @@ export class ExtendedAdapter extends BaseAdapter<
502
493
  bid_price = new Web3Number(bid.price, 0);
503
494
 
504
495
  return {
505
- status: false,
496
+ status: true,
506
497
  bid: bid_price, // check for quantity as well, not at the moment though
507
498
  ask: ask_price, // check for quantity as well, not at the moment though
508
499
  };
509
500
  } catch (err) {
510
- logger.error(`the err is: ${err}`);
501
+ logger.error(`extended orderbook fetch failed: ${err}`);
511
502
  return {
512
503
  status: false,
513
504
  bid: new Web3Number(0, 0),
@@ -521,33 +512,77 @@ export class ExtendedAdapter extends BaseAdapter<
521
512
  btcAmount: number,
522
513
  side: OrderSide,
523
514
  attempt = 1,
524
- maxAttempts = 5
515
+ maxAttempts = 5,
525
516
  ): Promise<{ position_id: string; btc_exposure: string } | null> {
526
517
  try {
527
518
  if (this.client === null) {
528
519
  logger.error("error initializing client");
529
520
  return null;
530
521
  }
531
- const setLeverage = await this.setLeverage(
532
- leverage,
533
- this.config.extendedMarketName
534
- );
535
- if (!setLeverage) {
536
- logger.error("error depositing or setting leverage");
537
- return null;
538
- }
539
- const positions = await this.getAllOpenPositions();
540
- if (positions === null) {
522
+ // todo, instead get levgerage and log errors if not ideal lever already
523
+ // const setLeverage = await this.setLeverage(
524
+ // leverage,
525
+ // this.config.extendedMarketName,
526
+ // );
527
+ // if (!setLeverage) {
528
+ // logger.error("error depositing or setting leverage");
529
+ // return null;
530
+ // }
531
+ const { ask, bid } = await this.fetchOrderBookFromExtended();
532
+ if (
533
+ !ask ||
534
+ !bid ||
535
+ ask.lessThanOrEqualTo(0) ||
536
+ bid.lessThanOrEqualTo(0)
537
+ ) {
538
+ logger.error(
539
+ `Invalid orderbook prices: ask=${ask?.toNumber()}, bid=${bid?.toNumber()}`,
540
+ );
541
541
  return null;
542
542
  }
543
- const { ask, bid } = await this.fetchOrderBookBTCUSDC();
543
+
544
544
  const spread = ask.minus(bid);
545
- let price = ask
546
- .plus(bid)
547
- .div(2)
548
- side === OrderSide.SELL ? price = price.minus(spread.times(0.2 * attempt)) : price = price.plus(spread.times(0.2 * attempt));
545
+ const midPrice = ask.plus(bid).div(2);
546
+ /** Maximum deviation: 50% of spread (to prevent extremely unfavorable prices) */
547
+ const MAX_PRICE_DEVIATION_MULTIPLIER = 0.5;
548
+ const priceAdjustmentMultiplier = Math.min(
549
+ 0.2 * attempt,
550
+ MAX_PRICE_DEVIATION_MULTIPLIER,
551
+ );
552
+ const priceAdjustment = spread.times(priceAdjustmentMultiplier);
553
+
554
+ let price = midPrice;
555
+ if (side === OrderSide.SELL) {
556
+ price = midPrice.minus(priceAdjustment);
557
+ } else {
558
+ price = midPrice.plus(priceAdjustment);
559
+ }
560
+
561
+ /** Validate price is still reasonable (within 50% of mid price) */
562
+ const maxDeviation = midPrice.times(0.5);
563
+ if (price.minus(midPrice).abs().greaterThan(maxDeviation)) {
564
+ logger.error(
565
+ `Price deviation too large on attempt ${attempt}: price=${price.toNumber()}, midPrice=${midPrice.toNumber()}, deviation=${price
566
+ .minus(midPrice)
567
+ .abs()
568
+ .toNumber()}`,
569
+ );
570
+ if (attempt >= maxAttempts) {
571
+ return null;
572
+ }
573
+ price =
574
+ side === OrderSide.SELL
575
+ ? midPrice.minus(maxDeviation)
576
+ : midPrice.plus(maxDeviation);
577
+ }
578
+
579
+ logger.info(
580
+ `createOrder attempt ${attempt}/${maxAttempts}: side=${side}, midPrice=${midPrice.toNumber()}, adjustedPrice=${price.toNumber()}, adjustment=${priceAdjustmentMultiplier * 100
581
+ }%`,
582
+ );
583
+
549
584
  const amount_in_token = (btcAmount * parseInt(leverage)).toFixed(
550
- this.config.extendedPrecision
585
+ this.config.extendedPrecision,
551
586
  ); // gives the amount of wbtc
552
587
 
553
588
  const result = await this.createExtendedPositon(
@@ -555,16 +590,68 @@ export class ExtendedAdapter extends BaseAdapter<
555
590
  this.config.extendedMarketName,
556
591
  amount_in_token,
557
592
  price.toFixed(0),
558
- side
593
+ side,
559
594
  );
560
- if (!result) {
595
+ if (!result || !result.position_id) {
596
+ logger.error("Failed to create order - no position_id returned");
561
597
  return null;
562
598
  }
563
- await new Promise((resolve) => setTimeout(resolve, 5000));
564
- const openOrder = await this.getOrderStatus(result.position_id, this.config.extendedMarketName);
565
- if (!openOrder || openOrder.status !== OrderStatus.FILLED) {
599
+
600
+ const positionId = result.position_id;
601
+ logger.info(
602
+ `Order created with position_id: ${positionId}. Waiting for API to update...`,
603
+ );
604
+
605
+ let openOrder = await this.getOrderStatus(
606
+ positionId,
607
+ this.config.extendedMarketName,
608
+ );
609
+ const maxStatusRetries = 3;
610
+ const statusRetryDelay = 5000;
611
+
612
+ if (!openOrder) {
613
+ logger.warn(
614
+ `Order ${positionId} not found in API yet. Retrying status fetch (max ${maxStatusRetries} times)...`,
615
+ );
616
+ for (
617
+ let statusRetry = 1;
618
+ statusRetry <= maxStatusRetries;
619
+ statusRetry++
620
+ ) {
621
+ await new Promise((resolve) => setTimeout(resolve, statusRetryDelay));
622
+ openOrder = await this.getOrderStatus(
623
+ positionId,
624
+ this.config.extendedMarketName,
625
+ );
626
+ if (openOrder) {
627
+ logger.info(
628
+ `Order ${positionId} found after ${statusRetry} status retry(ies)`,
629
+ );
630
+ break;
631
+ }
632
+ logger.warn(
633
+ `Order ${positionId} still not found after ${statusRetry}/${maxStatusRetries} status retries`,
634
+ );
635
+ }
636
+ }
637
+
638
+ if (openOrder && openOrder.status === OrderStatus.FILLED) {
639
+ logger.info(
640
+ `Order ${positionId} successfully filled with quantity ${openOrder.qty}`,
641
+ );
642
+ return {
643
+ position_id: positionId,
644
+ btc_exposure: openOrder.qty,
645
+ };
646
+ } else if (openOrder && openOrder.status !== OrderStatus.FILLED) {
647
+ // Order found but NOT FILLED - retry (recreate)
648
+ logger.warn(
649
+ `Order ${positionId} found but status is ${openOrder.status}, not FILLED. Retrying order creation...`,
650
+ );
566
651
  if (attempt >= maxAttempts) {
567
- logger.error("Max retries reached — could not verify open position");
652
+ logger.error(
653
+ `Max retries reached — order ${positionId} status is ${openOrder.status}, not FILLED`,
654
+ );
568
655
  return null;
569
656
  } else {
570
657
  const backoff = 2000 * attempt;
@@ -574,17 +661,22 @@ export class ExtendedAdapter extends BaseAdapter<
574
661
  btcAmount,
575
662
  side,
576
663
  attempt + 1,
577
- maxAttempts
664
+ maxAttempts,
578
665
  );
579
666
  }
580
667
  } else {
668
+ logger.warn(
669
+ `Order ${positionId} not found in API after ${maxStatusRetries} status retries (API update delayed ~30s). We got position_id from creation, so order exists. Returning position_id - status will be checked in next loop iteration.`,
670
+ );
581
671
  return {
582
- position_id: result.position_id,
583
- btc_exposure: openOrder.qty,
672
+ position_id: positionId,
673
+ btc_exposure: amount_in_token,
584
674
  };
585
675
  }
586
676
  } catch (err: any) {
587
- logger.error(`createShortOrder failed on attempt ${attempt}: ${err.message}`);
677
+ logger.error(
678
+ `createShortOrder failed on attempt ${attempt}: ${err.message}`,
679
+ );
588
680
 
589
681
  if (attempt < maxAttempts) {
590
682
  const backoff = 1200 * attempt;
@@ -595,7 +687,7 @@ export class ExtendedAdapter extends BaseAdapter<
595
687
  btcAmount,
596
688
  side,
597
689
  attempt + 1,
598
- maxAttempts
690
+ maxAttempts,
599
691
  );
600
692
  }
601
693
  logger.error("Max retry attempts reached — aborting createShortOrder");
@@ -608,19 +700,21 @@ export class ExtendedAdapter extends BaseAdapter<
608
700
  marketName: string,
609
701
  amount: string,
610
702
  price: string,
611
- side: OrderSide
703
+ side: OrderSide,
612
704
  ) {
613
705
  try {
614
706
  const result =
615
707
  side === OrderSide.SELL
616
708
  ? await client.createSellOrder(marketName, amount, price, {
617
- postOnly: false,
618
- timeInForce: TimeInForce.IOC,
619
- })
709
+ post_only: false,
710
+ reduce_only: false,
711
+ time_in_force: TimeInForce.IOC,
712
+ })
620
713
  : await client.createBuyOrder(marketName, amount, price, {
621
- postOnly: false,
622
- timeInForce: TimeInForce.IOC,
623
- });
714
+ post_only: false,
715
+ reduce_only: true,
716
+ time_in_force: TimeInForce.IOC,
717
+ });
624
718
  if (result.data.id) {
625
719
  const position_id = result.data.id.toString();
626
720
  return {
@@ -634,28 +728,108 @@ export class ExtendedAdapter extends BaseAdapter<
634
728
  }
635
729
  }
636
730
 
637
- async getDepositOrWithdrawalStatus(orderId: number | string, operationsType: AssetOperationType): Promise<boolean> {
638
- try{
639
- let transferHistory = await this.client.getAssetOperations({
640
- operationsType:[operationsType],
641
- operationsStatus:[AssetOperationStatus.COMPLETED]
642
- })
643
- if(operationsType === AssetOperationType.DEPOSIT){
644
- const myTransferStatus = transferHistory.data.find(operation => operation.transactionHash === orderId);
645
- if (!myTransferStatus) {
646
- return true;
731
+ async getDepositOrWithdrawalStatus(
732
+ orderId: number | string, // for deposits, send txn hash as string
733
+ operationsType: AssetOperationType,
734
+ ): Promise<boolean> {
735
+ const maxAttempts = 15;
736
+ const retryDelayMs = 30000; // 60 seconds
737
+
738
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
739
+ try {
740
+ let transferHistory = await this.client.getAssetOperations({
741
+ operationsType: [operationsType],
742
+ operationsStatus: [AssetOperationStatus.COMPLETED],
743
+ });
744
+ if (operationsType === AssetOperationType.DEPOSIT) {
745
+ const myTransferStatus = transferHistory.data.find(
746
+ (operation) =>
747
+ operation.transactionHash?.toLowerCase() ===
748
+ orderId.toString().toLowerCase(),
749
+ );
750
+ if (!myTransferStatus) {
751
+ if (attempt < maxAttempts) {
752
+ logger.info(
753
+ `Deposit operation not found for transactionHash ${orderId}, retrying (attempt ${attempt}/${maxAttempts})...`,
754
+ );
755
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
756
+ continue;
757
+ }
758
+ logger.warn(
759
+ `Deposit operation not found for transactionHash ${orderId} after ${maxAttempts} attempts`,
760
+ );
761
+ return false;
762
+ }
763
+ // Check if status is COMPLETED
764
+ if (myTransferStatus.status === AssetOperationStatus.COMPLETED) {
765
+ logger.info(`Deposit operation ${orderId} completed successfully`);
766
+ return true;
767
+ } else {
768
+ if (attempt < maxAttempts) {
769
+ logger.info(
770
+ `Deposit operation ${orderId} found but status is ${myTransferStatus.status}, not COMPLETED. Retrying (attempt ${attempt}/${maxAttempts})...`,
771
+ );
772
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
773
+ continue;
774
+ }
775
+ logger.warn(
776
+ `Deposit operation ${orderId} status is ${myTransferStatus.status} after ${maxAttempts} attempts, expected COMPLETED`,
777
+ );
778
+ return false;
779
+ }
780
+ } else {
781
+ const myTransferStatus = transferHistory.data.find(
782
+ (operation) => operation.id === orderId.toString(),
783
+ );
784
+ if (!myTransferStatus) {
785
+ if (attempt < maxAttempts) {
786
+ logger.info(
787
+ `Withdrawal status not found for orderId ${orderId} in completed operations, retrying (attempt ${attempt}/${maxAttempts})...`,
788
+ );
789
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
790
+ continue;
791
+ }
792
+ logger.warn(
793
+ `Withdrawal operation not found for orderId ${orderId} after ${maxAttempts} attempts`,
794
+ );
795
+ return false;
796
+ }
797
+ // Check if status is COMPLETED
798
+ if (myTransferStatus.status === AssetOperationStatus.COMPLETED) {
799
+ logger.info(
800
+ `Withdrawal operation ${orderId} completed successfully`,
801
+ );
802
+ return true;
803
+ } else {
804
+ if (attempt < maxAttempts) {
805
+ logger.info(
806
+ `Withdrawal operation ${orderId} found but status is ${myTransferStatus.status}, not COMPLETED. Retrying (attempt ${attempt}/${maxAttempts})...`,
807
+ );
808
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
809
+ continue;
810
+ }
811
+ logger.warn(
812
+ `Withdrawal operation ${orderId} status is ${myTransferStatus.status} after ${maxAttempts} attempts, expected COMPLETED`,
813
+ );
814
+ return false;
815
+ }
647
816
  }
648
- return true;
649
- }else{
650
- const myTransferStatus = transferHistory.data.find(operation => operation.id.toString() === orderId.toString());
651
- if (!myTransferStatus) {
652
- return true;
817
+ } catch (err) {
818
+ logger.error(
819
+ `error getting deposit or withdrawal status (attempt ${attempt}/${maxAttempts}): ${err}`,
820
+ );
821
+ if (attempt < maxAttempts) {
822
+ logger.info(`Retrying after ${retryDelayMs}ms...`);
823
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
824
+ continue;
653
825
  }
654
- return true;
826
+ logger.error(
827
+ `Max retry attempts reached for getDepositOrWithdrawalStatus`,
828
+ );
829
+ return false;
655
830
  }
656
- }catch(err){
657
- logger.error(`error getting deposit or withdrawal status: ${err}`);
658
- return false;
659
831
  }
832
+
833
+ return false;
660
834
  }
661
835
  }