@strkfarm/sdk 2.0.0-dev.2 → 2.0.0-dev.21
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/dist/index.browser.global.js +2006 -1062
- package/dist/index.browser.mjs +1845 -911
- package/dist/index.d.ts +144 -37
- package/dist/index.js +1853 -915
- package/dist/index.mjs +1845 -911
- package/package.json +1 -1
- package/src/modules/ExtendedWrapperSDk/types.ts +1 -1
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +39 -8
- package/src/modules/ekubo-quoter.ts +0 -12
- package/src/strategies/index.ts +2 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +17 -9
- package/src/strategies/universal-adapters/extended-adapter.ts +500 -146
- package/src/strategies/universal-adapters/index.ts +2 -1
- package/src/strategies/universal-adapters/vesu-adapter.ts +6 -6
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +778 -396
- package/src/strategies/universal-lst-muliplier-strategy.tsx +2 -1
- package/src/strategies/universal-strategy.tsx +5 -0
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +25 -16
- package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +36 -0
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +3 -6
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +50 -16
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +746 -305
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
5
10
|
import { Web3Number } from "@/dataTypes";
|
|
11
|
+
import { ApiResponse } from "@/modules/ExtendedWrapperSDk";
|
|
6
12
|
import { PositionInfo } from "./baseAdapter";
|
|
7
13
|
import { ManageCall } from "./baseAdapter";
|
|
8
14
|
import { ContractAddr } from "@/dataTypes";
|
|
@@ -11,7 +17,12 @@ import { StandardMerkleTree } from "@/utils";
|
|
|
11
17
|
import { hash, uint256 } from "starknet";
|
|
12
18
|
import { Global } from "@/global";
|
|
13
19
|
import { AdapterLeafType, GenerateCallFn } from "./baseAdapter";
|
|
14
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
AVNU_LEGACY_SANITIZER,
|
|
22
|
+
EXTENDED_SANITIZER,
|
|
23
|
+
SIMPLE_SANITIZER,
|
|
24
|
+
toBigInt,
|
|
25
|
+
} from "./adapter-utils";
|
|
15
26
|
import ExtendedWrapper, {
|
|
16
27
|
AssetOperationStatus,
|
|
17
28
|
AssetOperationType,
|
|
@@ -24,17 +35,21 @@ import { Balance, OpenOrder, OrderStatus } from "@/modules/ExtendedWrapperSDk";
|
|
|
24
35
|
import axios from "axios";
|
|
25
36
|
import { AvnuAdapter } from "./avnu-adapter";
|
|
26
37
|
import { logger } from "@/utils";
|
|
38
|
+
|
|
39
|
+
|
|
27
40
|
export interface ExtendedAdapterConfig extends BaseAdapterConfig {
|
|
28
41
|
vaultIdExtended: number;
|
|
29
42
|
extendedContract: ContractAddr; //0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
extendedBackendReadUrl: string;
|
|
44
|
+
extendedBackendWriteUrl: string;
|
|
32
45
|
extendedTimeout: number;
|
|
33
46
|
extendedRetries: number;
|
|
34
47
|
extendedBaseUrl: string;
|
|
35
48
|
extendedMarketName: string;
|
|
36
49
|
extendedPrecision: number;
|
|
37
50
|
avnuAdapter: AvnuAdapter;
|
|
51
|
+
retryDelayForOrderStatus: number;
|
|
52
|
+
minimumExtendedMovementAmount: number;
|
|
38
53
|
}
|
|
39
54
|
|
|
40
55
|
export class ExtendedAdapter extends BaseAdapter<
|
|
@@ -43,17 +58,23 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
43
58
|
> {
|
|
44
59
|
readonly config: ExtendedAdapterConfig;
|
|
45
60
|
readonly client: ExtendedWrapper;
|
|
61
|
+
readonly retryDelayForOrderStatus: number;
|
|
62
|
+
readonly minimumExtendedMovementAmount: number;
|
|
46
63
|
|
|
47
64
|
constructor(config: ExtendedAdapterConfig) {
|
|
48
65
|
super(config, ExtendedAdapter.name, Protocols.EXTENDED);
|
|
49
66
|
this.config = config as ExtendedAdapterConfig;
|
|
50
67
|
const client = new ExtendedWrapper({
|
|
51
|
-
baseUrl: this.config.
|
|
52
|
-
apiKey:
|
|
68
|
+
baseUrl: this.config.extendedBackendWriteUrl,
|
|
69
|
+
apiKey: "",
|
|
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;
|
|
57
78
|
}
|
|
58
79
|
//abstract means the method has no implementation in this class; instead, child classes must implement it.
|
|
59
80
|
protected async getAPY(
|
|
@@ -61,23 +82,40 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
61
82
|
): Promise<PositionAPY> {
|
|
62
83
|
/** Considering supportedPosiiton.isDebt as side parameter to get the funding rates */
|
|
63
84
|
const side = supportedPosition.isDebt ? "LONG" : "SHORT";
|
|
64
|
-
const fundingRates = await this.
|
|
65
|
-
if (fundingRates.
|
|
85
|
+
const fundingRates = await this.getFundingRates(side);
|
|
86
|
+
if (!fundingRates.success) {
|
|
66
87
|
logger.error("error getting funding rates", fundingRates);
|
|
67
88
|
return { apy: 0, type: APYType.BASE };
|
|
68
89
|
}
|
|
69
|
-
const fundingRate:FundingRate = fundingRates.data[0];
|
|
90
|
+
const fundingRate: FundingRate = fundingRates.data[0];
|
|
70
91
|
const apy = Number(fundingRate.f) * 365 * 24;
|
|
71
92
|
return { apy: apy, type: APYType.BASE };
|
|
72
93
|
}
|
|
73
94
|
|
|
95
|
+
public async getFundingRates(side: string, startTime?: number, endTime?: number): Promise<{ success: boolean, data: FundingRate[] }> {
|
|
96
|
+
try {
|
|
97
|
+
const response = await axios.get<ApiResponse<FundingRate[]>>(`${this.config.extendedBackendReadUrl}/fundingRates/${this.config.extendedMarketName}/${side}`, {
|
|
98
|
+
params: {
|
|
99
|
+
startTime: startTime ? startTime.toString() : undefined,
|
|
100
|
+
endTime: endTime ? endTime.toString() : undefined,
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
if (!response.data.success) {
|
|
104
|
+
logger.error("error getting funding rates", response.data);
|
|
105
|
+
return { success: false, data: [] };
|
|
106
|
+
}
|
|
107
|
+
logger.info("success getting funding rates", response.data.data);
|
|
108
|
+
return { success: true, data: response.data.data };
|
|
109
|
+
} catch (err) {
|
|
110
|
+
logger.error("error getting funding rates", err);
|
|
111
|
+
return { success: false, data: [] };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
74
115
|
protected async getPosition(
|
|
75
116
|
supportedPosition: SupportedPosition
|
|
76
117
|
): Promise<PositionAmount> {
|
|
77
|
-
|
|
78
|
-
throw new Error("Client not initialized");
|
|
79
|
-
}
|
|
80
|
-
const holdings = await this.getExtendedDepositAmount();
|
|
118
|
+
const holdings = await this.getExtendedDepositAmount()
|
|
81
119
|
if (!holdings) {
|
|
82
120
|
throw new Error("No position found");
|
|
83
121
|
}
|
|
@@ -85,7 +123,7 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
85
123
|
const amount = holdings.equity;
|
|
86
124
|
return Promise.resolve({
|
|
87
125
|
amount: new Web3Number(amount, 0),
|
|
88
|
-
remarks:
|
|
126
|
+
remarks: `Extended Equity`,
|
|
89
127
|
});
|
|
90
128
|
}
|
|
91
129
|
|
|
@@ -118,14 +156,14 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
118
156
|
sanitizer: ContractAddr;
|
|
119
157
|
id: string;
|
|
120
158
|
}[] {
|
|
121
|
-
const usdceToken = Global.getDefaultTokens().find(
|
|
159
|
+
const usdceToken = Global.getDefaultTokens().find(
|
|
160
|
+
(token) => token.symbol === "USDCe"
|
|
161
|
+
);
|
|
122
162
|
return [
|
|
123
163
|
{
|
|
124
164
|
target: this.config.supportedPositions[0].asset.address,
|
|
125
165
|
method: "approve",
|
|
126
|
-
packedArguments: [
|
|
127
|
-
AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
|
|
128
|
-
],
|
|
166
|
+
packedArguments: [AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt()],
|
|
129
167
|
id: `extended_approve_${this.config.supportedPositions[0].asset.symbol}`,
|
|
130
168
|
sanitizer: AVNU_LEGACY_SANITIZER,
|
|
131
169
|
},
|
|
@@ -155,19 +193,27 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
155
193
|
|
|
156
194
|
getSwapFromLegacyLeaf(): AdapterLeafType<DepositParams> {
|
|
157
195
|
const leafConfigs = this._getSwapFromLegacyLeaf();
|
|
158
|
-
const leaves = leafConfigs.map(config => {
|
|
196
|
+
const leaves = leafConfigs.map((config) => {
|
|
159
197
|
const { target, method, packedArguments, sanitizer, id } = config;
|
|
160
|
-
const leaf = this.constructSimpleLeafData(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
198
|
+
const leaf = this.constructSimpleLeafData(
|
|
199
|
+
{
|
|
200
|
+
id: id,
|
|
201
|
+
target,
|
|
202
|
+
method,
|
|
203
|
+
packedArguments,
|
|
204
|
+
},
|
|
205
|
+
sanitizer
|
|
206
|
+
);
|
|
166
207
|
return leaf;
|
|
167
208
|
});
|
|
168
|
-
return {
|
|
209
|
+
return {
|
|
210
|
+
leaves,
|
|
211
|
+
callConstructor: this.getSwapFromLegacyCall.bind(
|
|
212
|
+
this
|
|
213
|
+
) as unknown as GenerateCallFn<DepositParams>,
|
|
214
|
+
};
|
|
169
215
|
}
|
|
170
|
-
|
|
216
|
+
|
|
171
217
|
protected _getSwapFromLegacyLeaf(): {
|
|
172
218
|
target: ContractAddr;
|
|
173
219
|
method: string;
|
|
@@ -175,14 +221,14 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
175
221
|
sanitizer: ContractAddr;
|
|
176
222
|
id: string;
|
|
177
223
|
}[] {
|
|
178
|
-
const usdceToken = Global.getDefaultTokens().find(
|
|
224
|
+
const usdceToken = Global.getDefaultTokens().find(
|
|
225
|
+
(token) => token.symbol === "USDCe"
|
|
226
|
+
);
|
|
179
227
|
return [
|
|
180
228
|
{
|
|
181
229
|
target: usdceToken!.address,
|
|
182
230
|
method: "approve",
|
|
183
|
-
packedArguments: [
|
|
184
|
-
AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
|
|
185
|
-
],
|
|
231
|
+
packedArguments: [AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt()],
|
|
186
232
|
id: `extendedswaplegacyapprove_${usdceToken!.symbol}`,
|
|
187
233
|
sanitizer: AVNU_LEGACY_SANITIZER,
|
|
188
234
|
},
|
|
@@ -193,10 +239,9 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
193
239
|
id: `extended_swap_to_new_${usdceToken!.symbol}`,
|
|
194
240
|
sanitizer: AVNU_LEGACY_SANITIZER,
|
|
195
241
|
},
|
|
196
|
-
]
|
|
242
|
+
];
|
|
197
243
|
}
|
|
198
244
|
|
|
199
|
-
|
|
200
245
|
protected _getWithdrawLeaf(): {
|
|
201
246
|
target: ContractAddr;
|
|
202
247
|
method: string;
|
|
@@ -213,12 +258,14 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
213
258
|
async getDepositCall(params: DepositParams): Promise<ManageCall[]> {
|
|
214
259
|
try {
|
|
215
260
|
const usdcToken = this.config.supportedPositions[0].asset;
|
|
216
|
-
const usdceToken = Global.getDefaultTokens().find(
|
|
217
|
-
|
|
218
|
-
Math.random() * 10 ** usdcToken.decimals
|
|
261
|
+
const usdceToken = Global.getDefaultTokens().find(
|
|
262
|
+
(token) => token.symbol === "USDCe"
|
|
219
263
|
);
|
|
264
|
+
const salt = Math.floor(Math.random() * 10 ** usdcToken.decimals);
|
|
220
265
|
// Give approval for more amount than the required amount
|
|
221
|
-
const amount = uint256.bnToUint256(
|
|
266
|
+
const amount = uint256.bnToUint256(
|
|
267
|
+
params.amount.multipliedBy(10).toWei()
|
|
268
|
+
);
|
|
222
269
|
const quotes = await this.config.avnuAdapter.getQuotesAvnu(
|
|
223
270
|
usdcToken.address.toString(),
|
|
224
271
|
usdceToken!.address.toString(),
|
|
@@ -226,15 +273,17 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
226
273
|
this.config.avnuAdapter.config.vaultAllocator.address.toString(),
|
|
227
274
|
usdceToken!.decimals,
|
|
228
275
|
false
|
|
229
|
-
)
|
|
276
|
+
);
|
|
230
277
|
|
|
231
278
|
if (!quotes) {
|
|
232
279
|
logger.error("error getting quotes from avnu");
|
|
233
280
|
return [];
|
|
234
281
|
}
|
|
235
|
-
const getCalldata = await this.config.avnuAdapter.getSwapCallData(
|
|
282
|
+
const getCalldata = await this.config.avnuAdapter.getSwapCallData(
|
|
283
|
+
quotes!
|
|
284
|
+
);
|
|
236
285
|
const swapCallData = getCalldata[0];
|
|
237
|
-
|
|
286
|
+
//change extended sanitizer here
|
|
238
287
|
return [
|
|
239
288
|
{
|
|
240
289
|
sanitizer: AVNU_LEGACY_SANITIZER,
|
|
@@ -287,35 +336,44 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
287
336
|
}
|
|
288
337
|
}
|
|
289
338
|
|
|
290
|
-
getProofsForFromLegacySwap<T>(tree: StandardMerkleTree): {
|
|
339
|
+
getProofsForFromLegacySwap<T>(tree: StandardMerkleTree): {
|
|
340
|
+
proofs: string[][];
|
|
341
|
+
callConstructor:
|
|
342
|
+
| GenerateCallFn<DepositParams>
|
|
343
|
+
| GenerateCallFn<WithdrawParams>;
|
|
344
|
+
} {
|
|
291
345
|
let proofGroups: string[][] = [];
|
|
292
346
|
|
|
293
|
-
const ids = this.getSwapFromLegacyLeaf().leaves.map(l => l.readableId)
|
|
347
|
+
const ids = this.getSwapFromLegacyLeaf().leaves.map((l) => l.readableId);
|
|
294
348
|
// console.log(`${this.name}::getProofs ids: ${ids}`);
|
|
295
349
|
for (const [i, v] of tree.entries()) {
|
|
296
|
-
|
|
350
|
+
// console.log(`${this.name}::getProofs v: ${v.readableId}`);
|
|
297
351
|
if (ids.includes(v.readableId)) {
|
|
298
|
-
|
|
352
|
+
//console.log(`${this.name}::getProofs found id: ${v.readableId}`);
|
|
299
353
|
proofGroups.push(tree.getProof(i));
|
|
300
354
|
}
|
|
301
355
|
}
|
|
302
356
|
if (proofGroups.length != ids.length) {
|
|
303
|
-
throw new Error(`Not all proofs found for IDs: ${ids.join(
|
|
357
|
+
throw new Error(`Not all proofs found for IDs: ${ids.join(", ")}`);
|
|
304
358
|
}
|
|
305
359
|
|
|
306
360
|
// find leaf adapter
|
|
307
361
|
return {
|
|
308
362
|
proofs: proofGroups,
|
|
309
|
-
callConstructor: this.getSwapFromLegacyCall.bind(this)
|
|
310
|
-
|
|
311
|
-
|
|
363
|
+
callConstructor: this.getSwapFromLegacyCall.bind(this),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
312
366
|
|
|
313
367
|
async getSwapFromLegacyCall(params: DepositParams): Promise<ManageCall[]> {
|
|
314
368
|
try {
|
|
315
369
|
const usdcToken = this.config.supportedPositions[0].asset;
|
|
316
|
-
const usdceToken = Global.getDefaultTokens().find(
|
|
370
|
+
const usdceToken = Global.getDefaultTokens().find(
|
|
371
|
+
(token) => token.symbol === "USDCe"
|
|
372
|
+
);
|
|
317
373
|
// Give approval for more amount than the required amount
|
|
318
|
-
const amount = uint256.bnToUint256(
|
|
374
|
+
const amount = uint256.bnToUint256(
|
|
375
|
+
params.amount.multipliedBy(10).toWei()
|
|
376
|
+
);
|
|
319
377
|
const quotes = await this.config.avnuAdapter.getQuotesAvnu(
|
|
320
378
|
usdceToken!.address.toString(),
|
|
321
379
|
usdcToken!.address.toString(),
|
|
@@ -323,15 +381,17 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
323
381
|
this.config.avnuAdapter.config.vaultAllocator.address.toString(),
|
|
324
382
|
usdcToken!.decimals,
|
|
325
383
|
false
|
|
326
|
-
)
|
|
384
|
+
);
|
|
327
385
|
|
|
328
386
|
if (!quotes) {
|
|
329
387
|
logger.error("error getting quotes from avnu");
|
|
330
388
|
return [];
|
|
331
389
|
}
|
|
332
|
-
const getCalldata = await this.config.avnuAdapter.getSwapCallData(
|
|
390
|
+
const getCalldata = await this.config.avnuAdapter.getSwapCallData(
|
|
391
|
+
quotes!
|
|
392
|
+
);
|
|
333
393
|
const swapCallData = getCalldata[0];
|
|
334
|
-
|
|
394
|
+
//change extended sanitizer here
|
|
335
395
|
return [
|
|
336
396
|
{
|
|
337
397
|
sanitizer: AVNU_LEGACY_SANITIZER,
|
|
@@ -352,7 +412,7 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
352
412
|
selector: hash.getSelectorFromName("swap_to_new"),
|
|
353
413
|
calldata: swapCallData,
|
|
354
414
|
},
|
|
355
|
-
}
|
|
415
|
+
},
|
|
356
416
|
];
|
|
357
417
|
} catch (error) {
|
|
358
418
|
logger.error(`Error creating Deposit Call: ${error}`);
|
|
@@ -372,20 +432,105 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
372
432
|
}
|
|
373
433
|
}
|
|
374
434
|
|
|
375
|
-
async withdrawFromExtended(amount: Web3Number): Promise<
|
|
435
|
+
async withdrawFromExtended(amount: Web3Number): Promise<{
|
|
436
|
+
status: boolean;
|
|
437
|
+
receivedTxnHash: boolean;
|
|
438
|
+
}> {
|
|
376
439
|
try {
|
|
377
440
|
if (!this.client) {
|
|
378
|
-
|
|
441
|
+
logger.error("Client not initialized");
|
|
442
|
+
return {
|
|
443
|
+
status: false,
|
|
444
|
+
receivedTxnHash: false,
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (amount.lessThanOrEqualTo(0)) {
|
|
448
|
+
logger.error(
|
|
449
|
+
`Invalid withdrawal amount: ${amount.toNumber()}. Amount must be positive.`
|
|
450
|
+
);
|
|
451
|
+
return {
|
|
452
|
+
status: false,
|
|
453
|
+
receivedTxnHash: false,
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (amount.lessThanOrEqualTo(this.minimumExtendedMovementAmount)) {
|
|
457
|
+
logger.warn(
|
|
458
|
+
`Withdrawal amount ${amount.toNumber()} is below minimum Extended movement amount ${this.minimumExtendedMovementAmount}. Skipping withdrawal.`
|
|
459
|
+
);
|
|
460
|
+
return {
|
|
461
|
+
status: false,
|
|
462
|
+
receivedTxnHash: false,
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const holdings = await this.getExtendedDepositAmount();
|
|
466
|
+
if (!holdings) {
|
|
467
|
+
logger.error(
|
|
468
|
+
"Cannot get holdings - unable to validate withdrawal amount"
|
|
469
|
+
);
|
|
470
|
+
return {
|
|
471
|
+
status: false,
|
|
472
|
+
receivedTxnHash: false,
|
|
473
|
+
}
|
|
379
474
|
}
|
|
380
|
-
|
|
475
|
+
|
|
476
|
+
const availableForWithdrawal = parseFloat(
|
|
477
|
+
holdings.availableForWithdrawal
|
|
478
|
+
);
|
|
479
|
+
if (
|
|
480
|
+
!Number.isFinite(availableForWithdrawal) ||
|
|
481
|
+
availableForWithdrawal < 0
|
|
482
|
+
) {
|
|
483
|
+
logger.error(
|
|
484
|
+
`Invalid availableForWithdrawal: ${holdings.availableForWithdrawal}. Expected a finite, non-negative number.`
|
|
485
|
+
);
|
|
486
|
+
return {
|
|
487
|
+
status: false,
|
|
488
|
+
receivedTxnHash: false,
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const withdrawalAmount = amount.toNumber();
|
|
493
|
+
if (withdrawalAmount > availableForWithdrawal) {
|
|
494
|
+
logger.error(
|
|
495
|
+
`Withdrawal amount ${withdrawalAmount} exceeds available balance ${availableForWithdrawal}`
|
|
496
|
+
);
|
|
497
|
+
return {
|
|
498
|
+
status: false,
|
|
499
|
+
receivedTxnHash: false,
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
logger.info(
|
|
504
|
+
`Withdrawing ${withdrawalAmount} from Extended. Available balance: ${availableForWithdrawal}`
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
const withdrawalRequest = await this.client.withdrawUSDC(
|
|
508
|
+
amount.toFixed(2)
|
|
509
|
+
);
|
|
510
|
+
|
|
381
511
|
if (withdrawalRequest.status === "OK") {
|
|
382
|
-
const withdrawalStatus = await this.getDepositOrWithdrawalStatus(
|
|
383
|
-
|
|
512
|
+
const withdrawalStatus = await this.getDepositOrWithdrawalStatus(
|
|
513
|
+
withdrawalRequest.data,
|
|
514
|
+
AssetOperationType.WITHDRAWAL
|
|
515
|
+
);
|
|
516
|
+
return {
|
|
517
|
+
status: true,
|
|
518
|
+
receivedTxnHash: withdrawalStatus,
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
logger.error(
|
|
522
|
+
`Withdrawal request failed with status: ${withdrawalRequest.status}`
|
|
523
|
+
);
|
|
524
|
+
return {
|
|
525
|
+
status: false,
|
|
526
|
+
receivedTxnHash: false,
|
|
384
527
|
}
|
|
385
|
-
return false;
|
|
386
528
|
} catch (error) {
|
|
387
529
|
logger.error(`Error creating Withdraw Call: ${error}`);
|
|
388
|
-
return
|
|
530
|
+
return {
|
|
531
|
+
status: false,
|
|
532
|
+
receivedTxnHash: false,
|
|
533
|
+
}
|
|
389
534
|
}
|
|
390
535
|
}
|
|
391
536
|
|
|
@@ -394,21 +539,42 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
394
539
|
}
|
|
395
540
|
|
|
396
541
|
async getExtendedDepositAmount(): Promise<Balance | undefined> {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
542
|
+
try {
|
|
543
|
+
const result = await axios.get<ApiResponse<Balance>>(`${this.config.extendedBackendReadUrl}/holdings`);
|
|
544
|
+
if (!result.data) {
|
|
545
|
+
logger.error("error getting holdings - API returned null/undefined");
|
|
546
|
+
return undefined;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (!result.data.success) {
|
|
550
|
+
logger.error(
|
|
551
|
+
`error getting holdings - API returned status: ${result.status}`
|
|
552
|
+
);
|
|
553
|
+
return undefined;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const holdings = result.data.data;
|
|
557
|
+
if (!holdings) {
|
|
558
|
+
logger.warn(
|
|
559
|
+
"holdings data is null/undefined - treating as zero balance"
|
|
560
|
+
);
|
|
561
|
+
return {
|
|
562
|
+
collateral_name: "",
|
|
563
|
+
balance: "0",
|
|
564
|
+
equity: "0",
|
|
565
|
+
availableForTrade: "0",
|
|
566
|
+
availableForWithdrawal: "0",
|
|
567
|
+
unrealisedPnl: "0",
|
|
568
|
+
initialMargin: "0",
|
|
569
|
+
marginRatio: "0",
|
|
570
|
+
updatedTime: Date.now(),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
return holdings;
|
|
574
|
+
} catch (error) {
|
|
575
|
+
logger.error(`error getting holdings - exception: ${error}`);
|
|
409
576
|
return undefined;
|
|
410
577
|
}
|
|
411
|
-
return holdings;
|
|
412
578
|
}
|
|
413
579
|
|
|
414
580
|
async setLeverage(leverage: string, marketName: string): Promise<boolean> {
|
|
@@ -427,54 +593,62 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
427
593
|
}
|
|
428
594
|
|
|
429
595
|
async getAllOpenPositions(): Promise<Position[] | null> {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (response.data.length === 0) {
|
|
439
|
-
return [];
|
|
440
|
-
} else {
|
|
441
|
-
return response.data;
|
|
596
|
+
try {
|
|
597
|
+
const response = await axios.get<ApiResponse<Position[]>>(`${this.config.extendedBackendReadUrl}/positions`);
|
|
598
|
+
if (response.data.success) {
|
|
599
|
+
if (response.data.data.length === 0) {
|
|
600
|
+
return [];
|
|
601
|
+
} else {
|
|
602
|
+
return response.data.data;
|
|
603
|
+
}
|
|
442
604
|
}
|
|
605
|
+
return null;
|
|
606
|
+
} catch (err) {
|
|
607
|
+
logger.error("error getting all open positions", err);
|
|
608
|
+
return null;
|
|
443
609
|
}
|
|
444
|
-
return null;
|
|
445
610
|
}
|
|
446
611
|
|
|
447
612
|
async getOrderHistory(marketName: string): Promise<OpenOrder[] | null> {
|
|
448
|
-
|
|
449
|
-
|
|
613
|
+
try {
|
|
614
|
+
const result = await axios.get<ApiResponse<OpenOrder[]>>(`${this.config.extendedBackendReadUrl}/marketOrders/${marketName}`);
|
|
615
|
+
if (!result.data.success) {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
return result.data.data;
|
|
619
|
+
} catch (err) {
|
|
620
|
+
logger.error("error getting order history", err);
|
|
450
621
|
return null;
|
|
451
622
|
}
|
|
452
|
-
const result = await this.client.getOrderHistory(marketName);
|
|
453
|
-
return result.data;
|
|
454
623
|
}
|
|
455
624
|
|
|
456
625
|
async getOrderStatus(
|
|
457
626
|
orderId: string,
|
|
458
627
|
marketName: string
|
|
459
628
|
): Promise<OpenOrder | null> {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
629
|
+
try {
|
|
630
|
+
if (this.client === null) {
|
|
631
|
+
logger.error("error initializing client");
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
const orderhistory = await this.getOrderHistory(marketName);
|
|
465
635
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
636
|
+
if (!orderhistory || orderhistory.length === 0) {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
const order = orderhistory
|
|
640
|
+
.slice(0, 20)
|
|
641
|
+
.find((order) => order.id.toString() === orderId);
|
|
642
|
+
|
|
643
|
+
if (order) {
|
|
644
|
+
return order;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return null; // Order not found
|
|
648
|
+
} catch (error) {
|
|
649
|
+
logger.error(`error getting order status: ${error}`);
|
|
475
650
|
return null;
|
|
476
651
|
}
|
|
477
|
-
return order;
|
|
478
652
|
}
|
|
479
653
|
|
|
480
654
|
async fetchOrderBookBTCUSDC(): Promise<{
|
|
@@ -536,16 +710,59 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
536
710
|
logger.error("error depositing or setting leverage");
|
|
537
711
|
return null;
|
|
538
712
|
}
|
|
539
|
-
const
|
|
540
|
-
if (
|
|
713
|
+
const { ask, bid } = await this.fetchOrderBookBTCUSDC();
|
|
714
|
+
if (
|
|
715
|
+
!ask ||
|
|
716
|
+
!bid ||
|
|
717
|
+
ask.lessThanOrEqualTo(0) ||
|
|
718
|
+
bid.lessThanOrEqualTo(0)
|
|
719
|
+
) {
|
|
720
|
+
logger.error(
|
|
721
|
+
`Invalid orderbook prices: ask=${ask?.toNumber()}, bid=${bid?.toNumber()}`
|
|
722
|
+
);
|
|
541
723
|
return null;
|
|
542
724
|
}
|
|
543
|
-
|
|
725
|
+
|
|
544
726
|
const spread = ask.minus(bid);
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
727
|
+
const midPrice = ask.plus(bid).div(2);
|
|
728
|
+
/** Maximum deviation: 50% of spread (to prevent extremely unfavorable prices) */
|
|
729
|
+
const MAX_PRICE_DEVIATION_MULTIPLIER = 0.5;
|
|
730
|
+
const priceAdjustmentMultiplier = Math.min(
|
|
731
|
+
0.2 * attempt,
|
|
732
|
+
MAX_PRICE_DEVIATION_MULTIPLIER
|
|
733
|
+
);
|
|
734
|
+
const priceAdjustment = spread.times(priceAdjustmentMultiplier);
|
|
735
|
+
|
|
736
|
+
let price = midPrice;
|
|
737
|
+
if (side === OrderSide.SELL) {
|
|
738
|
+
price = midPrice.minus(priceAdjustment);
|
|
739
|
+
} else {
|
|
740
|
+
price = midPrice.plus(priceAdjustment);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/** Validate price is still reasonable (within 50% of mid price) */
|
|
744
|
+
const maxDeviation = midPrice.times(0.5);
|
|
745
|
+
if (price.minus(midPrice).abs().greaterThan(maxDeviation)) {
|
|
746
|
+
logger.error(
|
|
747
|
+
`Price deviation too large on attempt ${attempt}: price=${price.toNumber()}, midPrice=${midPrice.toNumber()}, deviation=${price
|
|
748
|
+
.minus(midPrice)
|
|
749
|
+
.abs()
|
|
750
|
+
.toNumber()}`
|
|
751
|
+
);
|
|
752
|
+
if (attempt >= maxAttempts) {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
price =
|
|
756
|
+
side === OrderSide.SELL
|
|
757
|
+
? midPrice.minus(maxDeviation)
|
|
758
|
+
: midPrice.plus(maxDeviation);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
logger.info(
|
|
762
|
+
`createOrder attempt ${attempt}/${maxAttempts}: side=${side}, midPrice=${midPrice.toNumber()}, adjustedPrice=${price.toNumber()}, adjustment=${priceAdjustmentMultiplier * 100
|
|
763
|
+
}%`
|
|
764
|
+
);
|
|
765
|
+
|
|
549
766
|
const amount_in_token = (btcAmount * parseInt(leverage)).toFixed(
|
|
550
767
|
this.config.extendedPrecision
|
|
551
768
|
); // gives the amount of wbtc
|
|
@@ -557,14 +774,66 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
557
774
|
price.toFixed(0),
|
|
558
775
|
side
|
|
559
776
|
);
|
|
560
|
-
if (!result) {
|
|
777
|
+
if (!result || !result.position_id) {
|
|
778
|
+
logger.error("Failed to create order - no position_id returned");
|
|
561
779
|
return null;
|
|
562
780
|
}
|
|
563
|
-
|
|
564
|
-
const
|
|
565
|
-
|
|
781
|
+
|
|
782
|
+
const positionId = result.position_id;
|
|
783
|
+
logger.info(
|
|
784
|
+
`Order created with position_id: ${positionId}. Waiting for API to update...`
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
let openOrder = await this.getOrderStatus(
|
|
788
|
+
positionId,
|
|
789
|
+
this.config.extendedMarketName
|
|
790
|
+
);
|
|
791
|
+
const maxStatusRetries = 3;
|
|
792
|
+
const statusRetryDelay = 5000;
|
|
793
|
+
|
|
794
|
+
if (!openOrder) {
|
|
795
|
+
logger.warn(
|
|
796
|
+
`Order ${positionId} not found in API yet. Retrying status fetch (max ${maxStatusRetries} times)...`
|
|
797
|
+
);
|
|
798
|
+
for (
|
|
799
|
+
let statusRetry = 1;
|
|
800
|
+
statusRetry <= maxStatusRetries;
|
|
801
|
+
statusRetry++
|
|
802
|
+
) {
|
|
803
|
+
await new Promise((resolve) => setTimeout(resolve, statusRetryDelay));
|
|
804
|
+
openOrder = await this.getOrderStatus(
|
|
805
|
+
positionId,
|
|
806
|
+
this.config.extendedMarketName
|
|
807
|
+
);
|
|
808
|
+
if (openOrder) {
|
|
809
|
+
logger.info(
|
|
810
|
+
`Order ${positionId} found after ${statusRetry} status retry(ies)`
|
|
811
|
+
);
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
logger.warn(
|
|
815
|
+
`Order ${positionId} still not found after ${statusRetry}/${maxStatusRetries} status retries`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (openOrder && openOrder.status === OrderStatus.FILLED) {
|
|
821
|
+
logger.info(
|
|
822
|
+
`Order ${positionId} successfully filled with quantity ${openOrder.qty}`
|
|
823
|
+
);
|
|
824
|
+
return {
|
|
825
|
+
position_id: positionId,
|
|
826
|
+
btc_exposure: openOrder.qty,
|
|
827
|
+
};
|
|
828
|
+
} else if (openOrder && openOrder.status !== OrderStatus.FILLED) {
|
|
829
|
+
// Order found but NOT FILLED - retry (recreate)
|
|
830
|
+
logger.warn(
|
|
831
|
+
`Order ${positionId} found but status is ${openOrder.status}, not FILLED. Retrying order creation...`
|
|
832
|
+
);
|
|
566
833
|
if (attempt >= maxAttempts) {
|
|
567
|
-
logger.error(
|
|
834
|
+
logger.error(
|
|
835
|
+
`Max retries reached — order ${positionId} status is ${openOrder.status}, not FILLED`
|
|
836
|
+
);
|
|
568
837
|
return null;
|
|
569
838
|
} else {
|
|
570
839
|
const backoff = 2000 * attempt;
|
|
@@ -578,13 +847,18 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
578
847
|
);
|
|
579
848
|
}
|
|
580
849
|
} else {
|
|
850
|
+
logger.warn(
|
|
851
|
+
`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.`
|
|
852
|
+
);
|
|
581
853
|
return {
|
|
582
|
-
position_id:
|
|
583
|
-
btc_exposure:
|
|
854
|
+
position_id: positionId,
|
|
855
|
+
btc_exposure: amount_in_token,
|
|
584
856
|
};
|
|
585
857
|
}
|
|
586
858
|
} catch (err: any) {
|
|
587
|
-
logger.error(
|
|
859
|
+
logger.error(
|
|
860
|
+
`createShortOrder failed on attempt ${attempt}: ${err.message}`
|
|
861
|
+
);
|
|
588
862
|
|
|
589
863
|
if (attempt < maxAttempts) {
|
|
590
864
|
const backoff = 1200 * attempt;
|
|
@@ -614,13 +888,13 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
614
888
|
const result =
|
|
615
889
|
side === OrderSide.SELL
|
|
616
890
|
? await client.createSellOrder(marketName, amount, price, {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
891
|
+
postOnly: false,
|
|
892
|
+
timeInForce: TimeInForce.IOC,
|
|
893
|
+
})
|
|
620
894
|
: await client.createBuyOrder(marketName, amount, price, {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
895
|
+
postOnly: false,
|
|
896
|
+
timeInForce: TimeInForce.IOC,
|
|
897
|
+
});
|
|
624
898
|
if (result.data.id) {
|
|
625
899
|
const position_id = result.data.id.toString();
|
|
626
900
|
return {
|
|
@@ -634,28 +908,108 @@ export class ExtendedAdapter extends BaseAdapter<
|
|
|
634
908
|
}
|
|
635
909
|
}
|
|
636
910
|
|
|
637
|
-
async getDepositOrWithdrawalStatus(
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
911
|
+
async getDepositOrWithdrawalStatus(
|
|
912
|
+
orderId: number | string, // for deposits, send txn hash as string
|
|
913
|
+
operationsType: AssetOperationType
|
|
914
|
+
): Promise<boolean> {
|
|
915
|
+
const maxAttempts = 15;
|
|
916
|
+
const retryDelayMs = 30000; // 60 seconds
|
|
917
|
+
|
|
918
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
919
|
+
try {
|
|
920
|
+
let transferHistory = await this.client.getAssetOperations({
|
|
921
|
+
operationsType: [operationsType],
|
|
922
|
+
operationsStatus: [AssetOperationStatus.COMPLETED],
|
|
923
|
+
});
|
|
924
|
+
if (operationsType === AssetOperationType.DEPOSIT) {
|
|
925
|
+
const myTransferStatus = transferHistory.data.find(
|
|
926
|
+
(operation) => operation.transactionHash?.toLowerCase() === orderId.toString().toLowerCase()
|
|
927
|
+
);
|
|
928
|
+
if (!myTransferStatus) {
|
|
929
|
+
if (attempt < maxAttempts) {
|
|
930
|
+
logger.info(
|
|
931
|
+
`Deposit operation not found for transactionHash ${orderId}, retrying (attempt ${attempt}/${maxAttempts})...`
|
|
932
|
+
);
|
|
933
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
logger.warn(
|
|
937
|
+
`Deposit operation not found for transactionHash ${orderId} after ${maxAttempts} attempts`
|
|
938
|
+
);
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
// Check if status is COMPLETED
|
|
942
|
+
if (myTransferStatus.status === AssetOperationStatus.COMPLETED) {
|
|
943
|
+
logger.info(
|
|
944
|
+
`Deposit operation ${orderId} completed successfully`
|
|
945
|
+
);
|
|
946
|
+
return true;
|
|
947
|
+
} else {
|
|
948
|
+
if (attempt < maxAttempts) {
|
|
949
|
+
logger.info(
|
|
950
|
+
`Deposit operation ${orderId} found but status is ${myTransferStatus.status}, not COMPLETED. Retrying (attempt ${attempt}/${maxAttempts})...`
|
|
951
|
+
);
|
|
952
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
logger.warn(
|
|
956
|
+
`Deposit operation ${orderId} status is ${myTransferStatus.status} after ${maxAttempts} attempts, expected COMPLETED`
|
|
957
|
+
);
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
} else {
|
|
961
|
+
const myTransferStatus = transferHistory.data.find(
|
|
962
|
+
(operation) => operation.id === orderId.toString()
|
|
963
|
+
);
|
|
964
|
+
if (!myTransferStatus) {
|
|
965
|
+
if (attempt < maxAttempts) {
|
|
966
|
+
logger.info(
|
|
967
|
+
`Withdrawal status not found for orderId ${orderId} in completed operations, retrying (attempt ${attempt}/${maxAttempts})...`
|
|
968
|
+
);
|
|
969
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
logger.warn(
|
|
973
|
+
`Withdrawal operation not found for orderId ${orderId} after ${maxAttempts} attempts`
|
|
974
|
+
);
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
// Check if status is COMPLETED
|
|
978
|
+
if (myTransferStatus.status === AssetOperationStatus.COMPLETED) {
|
|
979
|
+
logger.info(
|
|
980
|
+
`Withdrawal operation ${orderId} completed successfully`
|
|
981
|
+
);
|
|
982
|
+
return true;
|
|
983
|
+
} else {
|
|
984
|
+
if (attempt < maxAttempts) {
|
|
985
|
+
logger.info(
|
|
986
|
+
`Withdrawal operation ${orderId} found but status is ${myTransferStatus.status}, not COMPLETED. Retrying (attempt ${attempt}/${maxAttempts})...`
|
|
987
|
+
);
|
|
988
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
logger.warn(
|
|
992
|
+
`Withdrawal operation ${orderId} status is ${myTransferStatus.status} after ${maxAttempts} attempts, expected COMPLETED`
|
|
993
|
+
);
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
647
996
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
997
|
+
} catch (err) {
|
|
998
|
+
logger.error(
|
|
999
|
+
`error getting deposit or withdrawal status (attempt ${attempt}/${maxAttempts}): ${err}`
|
|
1000
|
+
);
|
|
1001
|
+
if (attempt < maxAttempts) {
|
|
1002
|
+
logger.info(`Retrying after ${retryDelayMs}ms...`);
|
|
1003
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
1004
|
+
continue;
|
|
653
1005
|
}
|
|
654
|
-
|
|
1006
|
+
logger.error(
|
|
1007
|
+
`Max retry attempts reached for getDepositOrWithdrawalStatus`
|
|
1008
|
+
);
|
|
1009
|
+
return false;
|
|
655
1010
|
}
|
|
656
|
-
}catch(err){
|
|
657
|
-
logger.error(`error getting deposit or withdrawal status: ${err}`);
|
|
658
|
-
return false;
|
|
659
1011
|
}
|
|
1012
|
+
|
|
1013
|
+
return false;
|
|
660
1014
|
}
|
|
661
1015
|
}
|