@strkfarm/sdk 1.1.70 → 2.0.0-dev.1
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/cli.js +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/index.browser.global.js +66861 -59746
- package/dist/index.browser.mjs +24970 -18579
- package/dist/index.d.ts +1969 -776
- package/dist/index.js +25264 -18850
- package/dist/index.mjs +25463 -19089
- package/package.json +80 -76
- package/src/data/extended-deposit.abi.json +3613 -0
- package/src/data/universal-vault.abi.json +135 -20
- package/src/dataTypes/address.ts +8 -1
- package/src/global.ts +240 -193
- package/src/interfaces/common.tsx +26 -2
- package/src/modules/ExtendedWrapperSDk/index.ts +62 -0
- package/src/modules/ExtendedWrapperSDk/types.ts +311 -0
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +395 -0
- package/src/modules/avnu.ts +17 -4
- package/src/modules/ekubo-quoter.ts +98 -10
- package/src/modules/erc20.ts +67 -21
- package/src/modules/harvests.ts +16 -29
- package/src/modules/index.ts +5 -1
- package/src/modules/lst-apr.ts +36 -0
- package/src/modules/midas.ts +159 -0
- package/src/modules/pricer-from-api.ts +2 -2
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +3 -38
- package/src/modules/token-market-data.ts +202 -0
- package/src/node/deployer.ts +1 -36
- package/src/strategies/autoCompounderStrk.ts +1 -1
- package/src/strategies/base-strategy.ts +20 -3
- package/src/strategies/ekubo-cl-vault.tsx +123 -306
- package/src/strategies/index.ts +4 -1
- package/src/strategies/svk-strategy.ts +247 -0
- package/src/strategies/universal-adapters/adapter-optimizer.ts +65 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +5 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +418 -0
- package/src/strategies/universal-adapters/baseAdapter.ts +181 -153
- package/src/strategies/universal-adapters/common-adapter.ts +98 -77
- package/src/strategies/universal-adapters/extended-adapter.ts +544 -0
- package/src/strategies/universal-adapters/index.ts +5 -1
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +109 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +220 -218
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +924 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +58 -51
- package/src/strategies/universal-lst-muliplier-strategy.tsx +707 -774
- package/src/strategies/universal-strategy.tsx +1098 -1180
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +28 -0
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +77 -0
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +48 -0
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +374 -0
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +992 -0
- package/src/strategies/vesu-rebalance.tsx +16 -19
- package/src/utils/health-factor-math.ts +11 -5
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { BaseAdapter, DepositParams, WithdrawParams, BaseAdapterConfig } from "./baseAdapter";
|
|
2
|
+
import { Protocols } from "@/interfaces";
|
|
3
|
+
import { SupportedPosition } from "./baseAdapter";
|
|
4
|
+
import { PositionAPY, APYType, PositionAmount } from "./baseAdapter";
|
|
5
|
+
import { Web3Number } from "@/dataTypes";
|
|
6
|
+
import { PositionInfo } from "./baseAdapter";
|
|
7
|
+
import { ManageCall } from "./baseAdapter";
|
|
8
|
+
import { ContractAddr } from "@/dataTypes";
|
|
9
|
+
import { AVNU_EXCHANGE_FOR_LEGACY_USDC } from "./adapter-utils";
|
|
10
|
+
import { hash, uint256 } from "starknet";
|
|
11
|
+
import { Global } from "@/global";
|
|
12
|
+
import { AVNU_LEGACY_SANITIZER, EXTENDED_SANITIZER, SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
|
|
13
|
+
import ExtendedWrapper, {
|
|
14
|
+
AssetOperationStatus,
|
|
15
|
+
AssetOperationType,
|
|
16
|
+
FundingRate,
|
|
17
|
+
OrderSide,
|
|
18
|
+
Position,
|
|
19
|
+
TimeInForce,
|
|
20
|
+
} from "@/modules/ExtendedWrapperSDk";
|
|
21
|
+
import { Balance, OpenOrder, OrderStatus } from "@/modules/ExtendedWrapperSDk";
|
|
22
|
+
import axios from "axios";
|
|
23
|
+
import { AvnuAdapter } from "./avnu-adapter";
|
|
24
|
+
import { logger } from "@/utils";
|
|
25
|
+
export interface ExtendedAdapterConfig extends BaseAdapterConfig {
|
|
26
|
+
vaultIdExtended: number;
|
|
27
|
+
extendedContract: ContractAddr; //0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f
|
|
28
|
+
extendedBackendUrl: string;
|
|
29
|
+
extendedApiKey: string;
|
|
30
|
+
extendedTimeout: number;
|
|
31
|
+
extendedRetries: number;
|
|
32
|
+
extendedBaseUrl: string;
|
|
33
|
+
extendedMarketName: string;
|
|
34
|
+
extendedPrecision: number;
|
|
35
|
+
avnuAdapter: AvnuAdapter;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class ExtendedAdapter extends BaseAdapter<
|
|
39
|
+
DepositParams,
|
|
40
|
+
WithdrawParams
|
|
41
|
+
> {
|
|
42
|
+
readonly config: ExtendedAdapterConfig;
|
|
43
|
+
readonly client: ExtendedWrapper;
|
|
44
|
+
|
|
45
|
+
constructor(config: ExtendedAdapterConfig) {
|
|
46
|
+
super(config, ExtendedAdapter.name, Protocols.EXTENDED);
|
|
47
|
+
this.config = config as ExtendedAdapterConfig;
|
|
48
|
+
const client = new ExtendedWrapper({
|
|
49
|
+
baseUrl: this.config.extendedBackendUrl,
|
|
50
|
+
apiKey: this.config.extendedApiKey,
|
|
51
|
+
timeout: this.config.extendedTimeout,
|
|
52
|
+
retries: this.config.extendedRetries,
|
|
53
|
+
});
|
|
54
|
+
this.client = client;
|
|
55
|
+
}
|
|
56
|
+
//abstract means the method has no implementation in this class; instead, child classes must implement it.
|
|
57
|
+
protected async getAPY(
|
|
58
|
+
supportedPosition: SupportedPosition
|
|
59
|
+
): Promise<PositionAPY> {
|
|
60
|
+
/** Considering supportedPosiiton.isDebt as side parameter to get the funding rates */
|
|
61
|
+
const side = supportedPosition.isDebt ? "LONG" : "SHORT";
|
|
62
|
+
const fundingRates = await this.client.getFundingRates(this.config.extendedMarketName, side);
|
|
63
|
+
if (fundingRates.status !== "OK") {
|
|
64
|
+
logger.error("error getting funding rates", fundingRates);
|
|
65
|
+
return { apy: 0, type: APYType.BASE };
|
|
66
|
+
}
|
|
67
|
+
const fundingRate:FundingRate = fundingRates.data[0];
|
|
68
|
+
const apy = Number(fundingRate.f) * 365 * 24;
|
|
69
|
+
return { apy: apy, type: APYType.BASE };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected async getPosition(
|
|
73
|
+
supportedPosition: SupportedPosition
|
|
74
|
+
): Promise<PositionAmount> {
|
|
75
|
+
if (!this.client) {
|
|
76
|
+
throw new Error("Client not initialized");
|
|
77
|
+
}
|
|
78
|
+
const holdings = await this.getExtendedDepositAmount();
|
|
79
|
+
if (!holdings) {
|
|
80
|
+
throw new Error("No position found");
|
|
81
|
+
}
|
|
82
|
+
// equity is the total amount on extended
|
|
83
|
+
const amount = holdings.equity;
|
|
84
|
+
return Promise.resolve({
|
|
85
|
+
amount: new Web3Number(amount, 0),
|
|
86
|
+
remarks: `${holdings.availableForWithdrawal} ${holdings.equity}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async maxDeposit(amount?: Web3Number): Promise<PositionInfo> {
|
|
91
|
+
return Promise.resolve({
|
|
92
|
+
tokenInfo: this.config.baseToken,
|
|
93
|
+
amount: new Web3Number(0, 0),
|
|
94
|
+
usdValue: 0,
|
|
95
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
96
|
+
protocol: Protocols.EXTENDED,
|
|
97
|
+
remarks: "",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async maxWithdraw(): Promise<PositionInfo> {
|
|
102
|
+
return Promise.resolve({
|
|
103
|
+
tokenInfo: this.config.baseToken,
|
|
104
|
+
amount: new Web3Number(0, 0),
|
|
105
|
+
usdValue: 0,
|
|
106
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
107
|
+
protocol: Protocols.EXTENDED,
|
|
108
|
+
remarks: "",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
protected _getDepositLeaf(): {
|
|
113
|
+
target: ContractAddr;
|
|
114
|
+
method: string;
|
|
115
|
+
packedArguments: bigint[];
|
|
116
|
+
sanitizer: ContractAddr;
|
|
117
|
+
id: string;
|
|
118
|
+
}[] {
|
|
119
|
+
const usdceToken = Global.getDefaultTokens().find(token => token.symbol === "USDCe");
|
|
120
|
+
return [
|
|
121
|
+
{
|
|
122
|
+
target: this.config.supportedPositions[0].asset.address,
|
|
123
|
+
method: "approve",
|
|
124
|
+
packedArguments: [
|
|
125
|
+
AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
|
|
126
|
+
],
|
|
127
|
+
id: `extended_approve_${this.config.supportedPositions[0].asset.symbol}`,
|
|
128
|
+
sanitizer: AVNU_LEGACY_SANITIZER,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
target: AVNU_EXCHANGE_FOR_LEGACY_USDC,
|
|
132
|
+
method: "swap_to_legacy",
|
|
133
|
+
packedArguments: [],
|
|
134
|
+
id: `extended_swap_to_legacy_${this.config.supportedPositions[0].asset.symbol}`,
|
|
135
|
+
sanitizer: AVNU_LEGACY_SANITIZER,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
target: usdceToken!.address,
|
|
139
|
+
method: "approve",
|
|
140
|
+
packedArguments: [this.config.extendedContract.toBigInt()],
|
|
141
|
+
id: `extended_approve_${usdceToken!.symbol}`,
|
|
142
|
+
sanitizer: SIMPLE_SANITIZER,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
target: this.config.extendedContract,
|
|
146
|
+
method: "deposit",
|
|
147
|
+
packedArguments: [BigInt(this.config.vaultIdExtended)],
|
|
148
|
+
sanitizer: EXTENDED_SANITIZER,
|
|
149
|
+
id: `extended_deposit_${usdceToken!.symbol}`,
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
protected _getWithdrawLeaf(): {
|
|
155
|
+
target: ContractAddr;
|
|
156
|
+
method: string;
|
|
157
|
+
packedArguments: bigint[];
|
|
158
|
+
sanitizer: ContractAddr;
|
|
159
|
+
id: string;
|
|
160
|
+
}[] {
|
|
161
|
+
/**
|
|
162
|
+
* Withdraw is done via extended wrapper
|
|
163
|
+
*/
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getDepositCall(params: DepositParams): Promise<ManageCall[]> {
|
|
168
|
+
try {
|
|
169
|
+
const usdcToken = this.config.supportedPositions[0].asset;
|
|
170
|
+
const usdceToken = Global.getDefaultTokens().find(token => token.symbol === "USDCe");
|
|
171
|
+
const salt = Math.floor(
|
|
172
|
+
Math.random() * 10 ** usdcToken.decimals
|
|
173
|
+
);
|
|
174
|
+
const amount = uint256.bnToUint256(params.amount.multipliedBy(10).toWei());
|
|
175
|
+
const quotes = await this.config.avnuAdapter.getQuotesAvnu(
|
|
176
|
+
usdcToken.address.toString(),
|
|
177
|
+
usdceToken!.address.toString(),
|
|
178
|
+
params.amount.toNumber(),
|
|
179
|
+
this.config.avnuAdapter.config.vaultAllocator.address.toString(),
|
|
180
|
+
usdceToken!.decimals,
|
|
181
|
+
false
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if (!quotes) {
|
|
185
|
+
logger.error("error getting quotes from avnu");
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const getCalldata = await this.config.avnuAdapter.getSwapCallData(quotes!);
|
|
190
|
+
const swapCallData = getCalldata[0];
|
|
191
|
+
//change extended sanitizer here
|
|
192
|
+
return [
|
|
193
|
+
{
|
|
194
|
+
sanitizer: AVNU_LEGACY_SANITIZER,
|
|
195
|
+
call: {
|
|
196
|
+
contractAddress: usdcToken.address,
|
|
197
|
+
selector: hash.getSelectorFromName("approve"),
|
|
198
|
+
calldata: [
|
|
199
|
+
AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
|
|
200
|
+
toBigInt(amount.low.toString()), // amount low
|
|
201
|
+
toBigInt(amount.high.toString()), // amount high
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
sanitizer: AVNU_LEGACY_SANITIZER,
|
|
207
|
+
call: {
|
|
208
|
+
contractAddress: AVNU_EXCHANGE_FOR_LEGACY_USDC,
|
|
209
|
+
selector: hash.getSelectorFromName("swap_to_legacy"),
|
|
210
|
+
calldata: swapCallData,
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
sanitizer: SIMPLE_SANITIZER,
|
|
215
|
+
call: {
|
|
216
|
+
contractAddress: usdceToken!.address,
|
|
217
|
+
selector: hash.getSelectorFromName("approve"),
|
|
218
|
+
calldata: [
|
|
219
|
+
this.config.extendedContract.toBigInt(),
|
|
220
|
+
toBigInt(amount.low.toString()), // amount low
|
|
221
|
+
toBigInt(amount.high.toString()), // amount high
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
sanitizer: EXTENDED_SANITIZER,
|
|
227
|
+
call: {
|
|
228
|
+
contractAddress: this.config.extendedContract,
|
|
229
|
+
selector: hash.getSelectorFromName("deposit"),
|
|
230
|
+
calldata: [
|
|
231
|
+
BigInt(this.config.vaultIdExtended),
|
|
232
|
+
BigInt(params.amount.toWei()),
|
|
233
|
+
BigInt(salt),
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
];
|
|
238
|
+
} catch (error) {
|
|
239
|
+
logger.error(`Error creating Deposit Call: ${error}`);
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
//Swap wbtc to usdc
|
|
245
|
+
async getWithdrawCall(params: WithdrawParams): Promise<ManageCall[]> {
|
|
246
|
+
try {
|
|
247
|
+
if (!this.client) {
|
|
248
|
+
throw new Error("Client not initialized");
|
|
249
|
+
}
|
|
250
|
+
return [];
|
|
251
|
+
} catch (error) {
|
|
252
|
+
logger.error(`Error creating Withdraw Call: ${error}`);
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async withdrawFromExtended(amount: Web3Number): Promise<boolean> {
|
|
258
|
+
try {
|
|
259
|
+
if (!this.client) {
|
|
260
|
+
throw new Error("Client not initialized");
|
|
261
|
+
}
|
|
262
|
+
const withdrawalRequest = this.client.withdrawUSDC(amount.toString());
|
|
263
|
+
if ((await withdrawalRequest).status === "OK") {
|
|
264
|
+
console.log("Withdrawal request successful");
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
return false;
|
|
268
|
+
} catch (error) {
|
|
269
|
+
logger.error(`Error creating Withdraw Call: ${error}`);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async getHealthFactor(): Promise<number> {
|
|
275
|
+
return Promise.resolve(1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async getExtendedDepositAmount(): Promise<Balance | undefined> {
|
|
279
|
+
if (this.client === null) {
|
|
280
|
+
logger.error("error initializing client");
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
const result = await this.client.getHoldings();
|
|
284
|
+
if (!result) {
|
|
285
|
+
logger.error(`error getting holdings: ${result}`);
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
const holdings = result.data;
|
|
289
|
+
if (!holdings) {
|
|
290
|
+
logger.error(`error getting holdings: ${holdings}`);
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
return holdings;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async setLeverage(leverage: string, marketName: string): Promise<boolean> {
|
|
297
|
+
if (this.client === null) {
|
|
298
|
+
logger.error("error initializing client");
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
const res = await this.client.updateLeverage({
|
|
302
|
+
leverage: leverage,
|
|
303
|
+
market: marketName,
|
|
304
|
+
});
|
|
305
|
+
if (res.status === "OK") {
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async getAllOpenPositions(): Promise<Position[] | null> {
|
|
312
|
+
if (this.client === null) {
|
|
313
|
+
logger.error("error initializing client");
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
const response = await this.client.getPositionsForMarket(
|
|
317
|
+
this.config.extendedMarketName
|
|
318
|
+
);
|
|
319
|
+
if (response.status === "OK") {
|
|
320
|
+
if (response.data.length === 0) {
|
|
321
|
+
return [];
|
|
322
|
+
} else {
|
|
323
|
+
return response.data;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async getOrderHistory(marketName: string): Promise<OpenOrder[] | null> {
|
|
330
|
+
if (this.client === null) {
|
|
331
|
+
logger.error("error initializing client");
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const result = await this.client.getOrderHistory(marketName);
|
|
335
|
+
return result.data;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async getOrderStatus(
|
|
339
|
+
orderId: string,
|
|
340
|
+
marketName: string
|
|
341
|
+
): Promise<OpenOrder | null> {
|
|
342
|
+
if (this.client === null) {
|
|
343
|
+
logger.error("error initializing client");
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
const orderhistory = await this.getOrderHistory(marketName);
|
|
347
|
+
|
|
348
|
+
if (!orderhistory || orderhistory.length === 0) {
|
|
349
|
+
logger.error(`error getting order: ${orderId}`);
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
const order = orderhistory
|
|
353
|
+
.slice(0, 3)
|
|
354
|
+
.find((order) => order.id.toString() === orderId);
|
|
355
|
+
if (!order) {
|
|
356
|
+
logger.error(`error getting order: ${order}`);
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
return order;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async fetchOrderBookBTCUSDC(): Promise<{
|
|
363
|
+
status: boolean;
|
|
364
|
+
bid: Web3Number;
|
|
365
|
+
ask: Web3Number;
|
|
366
|
+
}> {
|
|
367
|
+
try {
|
|
368
|
+
const res = await axios.get(
|
|
369
|
+
`${this.config.extendedBaseUrl}/api/v1/info/markets/${this.config.extendedMarketName}/orderbook`
|
|
370
|
+
);
|
|
371
|
+
let ask_price = new Web3Number(0, 0);
|
|
372
|
+
let bid_price = new Web3Number(0, 0);
|
|
373
|
+
if (res.data.status !== "OK") {
|
|
374
|
+
return {
|
|
375
|
+
status: false,
|
|
376
|
+
bid: bid_price,
|
|
377
|
+
ask: ask_price,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
const data = res.data.data;
|
|
381
|
+
const bid = data.bid[0];
|
|
382
|
+
const ask = data.ask[0];
|
|
383
|
+
ask_price = new Web3Number(ask.price, 0);
|
|
384
|
+
bid_price = new Web3Number(bid.price, 0);
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
status: false,
|
|
388
|
+
bid: bid_price, // check for quantity as well, not at the moment though
|
|
389
|
+
ask: ask_price, // check for quantity as well, not at the moment though
|
|
390
|
+
};
|
|
391
|
+
} catch (err) {
|
|
392
|
+
logger.error(`the err is: ${err}`);
|
|
393
|
+
return {
|
|
394
|
+
status: false,
|
|
395
|
+
bid: new Web3Number(0, 0),
|
|
396
|
+
ask: new Web3Number(0, 0),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async createOrder(
|
|
402
|
+
leverage: string,
|
|
403
|
+
btcAmount: number,
|
|
404
|
+
side: OrderSide,
|
|
405
|
+
attempt = 1,
|
|
406
|
+
maxAttempts = 5
|
|
407
|
+
): Promise<{ position_id: string; btc_exposure: string } | null> {
|
|
408
|
+
try {
|
|
409
|
+
if (this.client === null) {
|
|
410
|
+
logger.error("error initializing client");
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
const setLeverage = await this.setLeverage(
|
|
414
|
+
leverage,
|
|
415
|
+
this.config.extendedMarketName
|
|
416
|
+
);
|
|
417
|
+
if (!setLeverage) {
|
|
418
|
+
logger.error("error depositing or setting leverage");
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
const positions = await this.getAllOpenPositions();
|
|
422
|
+
if (positions === null) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
const { ask, bid } = await this.fetchOrderBookBTCUSDC();
|
|
426
|
+
const spread = ask.minus(bid);
|
|
427
|
+
let price = ask
|
|
428
|
+
.plus(bid)
|
|
429
|
+
.div(2)
|
|
430
|
+
side === OrderSide.SELL ? price = price.minus(spread.times(0.2 * attempt)) : price = price.plus(spread.times(0.2 * attempt));
|
|
431
|
+
const amount_in_token = (btcAmount * parseInt(leverage)).toFixed(
|
|
432
|
+
this.config.extendedPrecision
|
|
433
|
+
); // gives the amount of wbtc
|
|
434
|
+
|
|
435
|
+
const result = await this.createExtendedPositon(
|
|
436
|
+
this.client,
|
|
437
|
+
this.config.extendedMarketName,
|
|
438
|
+
amount_in_token,
|
|
439
|
+
price.toFixed(0),
|
|
440
|
+
side
|
|
441
|
+
);
|
|
442
|
+
if (!result) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
446
|
+
const openOrder = await this.getOrderStatus(result.position_id, this.config.extendedMarketName);
|
|
447
|
+
if (!openOrder || openOrder.status !== OrderStatus.FILLED) {
|
|
448
|
+
if (attempt >= maxAttempts) {
|
|
449
|
+
logger.error("Max retries reached — could not verify open position");
|
|
450
|
+
return null;
|
|
451
|
+
} else {
|
|
452
|
+
const backoff = 2000 * attempt;
|
|
453
|
+
await new Promise((resolve) => setTimeout(resolve, backoff));
|
|
454
|
+
return this.createOrder(
|
|
455
|
+
leverage,
|
|
456
|
+
btcAmount,
|
|
457
|
+
side,
|
|
458
|
+
attempt + 1,
|
|
459
|
+
maxAttempts
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
return {
|
|
464
|
+
position_id: result.position_id,
|
|
465
|
+
btc_exposure: openOrder.qty,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
} catch (err: any) {
|
|
469
|
+
logger.error(`createShortOrder failed on attempt ${attempt}: ${err.message}`);
|
|
470
|
+
|
|
471
|
+
if (attempt < maxAttempts) {
|
|
472
|
+
const backoff = 1200 * attempt;
|
|
473
|
+
console.log(`Retrying after ${backoff}ms...`);
|
|
474
|
+
await new Promise((resolve) => setTimeout(resolve, backoff));
|
|
475
|
+
return this.createOrder(
|
|
476
|
+
leverage,
|
|
477
|
+
btcAmount,
|
|
478
|
+
side,
|
|
479
|
+
attempt + 1,
|
|
480
|
+
maxAttempts
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
logger.error("Max retry attempts reached — aborting createShortOrder");
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async createExtendedPositon(
|
|
489
|
+
client: ExtendedWrapper,
|
|
490
|
+
marketName: string,
|
|
491
|
+
amount: string,
|
|
492
|
+
price: string,
|
|
493
|
+
side: OrderSide
|
|
494
|
+
) {
|
|
495
|
+
try {
|
|
496
|
+
const result =
|
|
497
|
+
side === OrderSide.SELL
|
|
498
|
+
? await client.createSellOrder(marketName, amount, price, {
|
|
499
|
+
postOnly: false,
|
|
500
|
+
timeInForce: TimeInForce.IOC,
|
|
501
|
+
})
|
|
502
|
+
: await client.createBuyOrder(marketName, amount, price, {
|
|
503
|
+
postOnly: false,
|
|
504
|
+
timeInForce: TimeInForce.IOC,
|
|
505
|
+
});
|
|
506
|
+
if (result.data.id) {
|
|
507
|
+
const position_id = result.data.id.toString();
|
|
508
|
+
return {
|
|
509
|
+
position_id,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
return null;
|
|
513
|
+
} catch (err) {
|
|
514
|
+
console.log("Error opening short extended position", err);
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async getDepositOrWithdrawalStatus(orderId: number | string, operationsType: AssetOperationType): Promise<boolean> {
|
|
520
|
+
try{
|
|
521
|
+
let transferHistory = await this.client.getAssetOperations({
|
|
522
|
+
operationsType:[operationsType],
|
|
523
|
+
operationsStatus:[AssetOperationStatus.COMPLETED]
|
|
524
|
+
})
|
|
525
|
+
console.log("transferHistory", transferHistory);
|
|
526
|
+
if(operationsType === AssetOperationType.DEPOSIT){
|
|
527
|
+
const myTransferStatus = transferHistory.data.find(operation => operation.transactionHash === orderId);
|
|
528
|
+
if (!myTransferStatus) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
return true;
|
|
532
|
+
}else{
|
|
533
|
+
const myTransferStatus = transferHistory.data.find(operation => operation.id === orderId);
|
|
534
|
+
if (!myTransferStatus) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
}catch(err){
|
|
540
|
+
logger.error(`error getting deposit or withdrawal status: ${err}`);
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export * from "./baseAdapter";
|
|
2
2
|
export * from "./common-adapter";
|
|
3
3
|
export * from "./vesu-adapter";
|
|
4
|
-
export * from "./adapter
|
|
4
|
+
export * from "./vesu-supply-only-adapter";
|
|
5
|
+
export * from "./vesu-multiply-adapter";
|
|
6
|
+
export * from "./extended-adapter";
|
|
7
|
+
export * from "./adapter-utils";
|
|
8
|
+
export * from "./unused-balance-adapter";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
2
|
+
import { APYType, BaseAdapter, BaseAdapterConfig, DepositParams, ManageCall, PositionAmount, PositionAPY, PositionInfo, SupportedPosition, WithdrawParams } from "./baseAdapter";
|
|
3
|
+
import { Contract, uint256 } from "starknet";
|
|
4
|
+
import erc20Abi from "@/data/erc20.abi.json";
|
|
5
|
+
import { ERC20, TokenMarketData } from "@/modules";
|
|
6
|
+
import { Protocols } from "@/interfaces";
|
|
7
|
+
|
|
8
|
+
export interface UnusedBalanceAdapterConfig extends BaseAdapterConfig {}
|
|
9
|
+
|
|
10
|
+
export class UnusedBalanceAdapter extends BaseAdapter<DepositParams, WithdrawParams> {
|
|
11
|
+
readonly config: UnusedBalanceAdapterConfig;
|
|
12
|
+
readonly tokenMarketData: TokenMarketData;
|
|
13
|
+
|
|
14
|
+
constructor(config: UnusedBalanceAdapterConfig) {
|
|
15
|
+
super(config, UnusedBalanceAdapter.name, Protocols.NONE);
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.tokenMarketData = new TokenMarketData(this.config.pricer, this.config.networkConfig);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected async getAPY(_supportedPosition: SupportedPosition): Promise<PositionAPY> {
|
|
21
|
+
const isSupported = this.tokenMarketData.isAPYSupported(this.config.baseToken);
|
|
22
|
+
const apy = isSupported ? await this.tokenMarketData.getAPY(this.config.baseToken) : 0;
|
|
23
|
+
return { apy: apy, type: isSupported ? APYType.LST : APYType.BASE };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected async getPosition(supportedPosition: SupportedPosition): Promise<PositionAmount> {
|
|
27
|
+
try {
|
|
28
|
+
const balance = await (new ERC20(this.config.networkConfig)).balanceOf(supportedPosition.asset.address, this.config.vaultAllocator.address, supportedPosition.asset.decimals);
|
|
29
|
+
return {
|
|
30
|
+
amount: balance,
|
|
31
|
+
remarks: "Unused balance"
|
|
32
|
+
}
|
|
33
|
+
} catch (_e) {
|
|
34
|
+
throw new Error(`${UnusedBalanceAdapter.name}: Failed to get position for ${supportedPosition.asset.symbol}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async maxDeposit(amount?: Web3Number): Promise<PositionInfo> {
|
|
39
|
+
const baseToken = this.config.baseToken;
|
|
40
|
+
if (!amount) {
|
|
41
|
+
const infinite = new Web3Number('999999999999999999999999999', baseToken.decimals);
|
|
42
|
+
return {
|
|
43
|
+
tokenInfo: baseToken,
|
|
44
|
+
amount: infinite,
|
|
45
|
+
usdValue: Number.POSITIVE_INFINITY,
|
|
46
|
+
remarks: "Max deposit (infinity)",
|
|
47
|
+
apy: await this.getAPY({ asset: baseToken, isDebt: false }),
|
|
48
|
+
protocol: this.protocol
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const usdValue = await this.getUSDValue(baseToken, amount);
|
|
52
|
+
return {
|
|
53
|
+
tokenInfo: baseToken,
|
|
54
|
+
amount,
|
|
55
|
+
usdValue,
|
|
56
|
+
remarks: "Deposit amount",
|
|
57
|
+
apy: await this.getAPY({ asset: baseToken, isDebt: false }),
|
|
58
|
+
protocol: this.protocol
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async maxWithdraw(): Promise<PositionInfo> {
|
|
63
|
+
const baseToken = this.config.baseToken;
|
|
64
|
+
const current = await this.getPosition({ asset: baseToken, isDebt: false });
|
|
65
|
+
const usdValue = await this.getUSDValue(baseToken, current.amount);
|
|
66
|
+
return {
|
|
67
|
+
tokenInfo: baseToken,
|
|
68
|
+
amount: current.amount,
|
|
69
|
+
usdValue,
|
|
70
|
+
remarks: "Max withdraw",
|
|
71
|
+
apy: await this.getAPY({ asset: baseToken, isDebt: false }),
|
|
72
|
+
protocol: this.protocol
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
protected _getDepositLeaf(): {
|
|
77
|
+
target: ContractAddr,
|
|
78
|
+
method: string,
|
|
79
|
+
packedArguments: bigint[];
|
|
80
|
+
sanitizer: ContractAddr,
|
|
81
|
+
id: string
|
|
82
|
+
}[] {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
protected _getWithdrawLeaf(): {
|
|
87
|
+
target: ContractAddr,
|
|
88
|
+
method: string,
|
|
89
|
+
packedArguments: bigint[];
|
|
90
|
+
sanitizer: ContractAddr,
|
|
91
|
+
id: string
|
|
92
|
+
}[] {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getHealthFactor(): Promise<number> {
|
|
97
|
+
return Promise.resolve(10);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getDepositCall(params: DepositParams): Promise<ManageCall[]> {
|
|
101
|
+
return Promise.resolve([]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getWithdrawCall(params: WithdrawParams): Promise<ManageCall[]> {
|
|
105
|
+
return Promise.resolve([]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|