aiia-vault-sdk 1.0.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.
Files changed (39) hide show
  1. package/SeedRoundFundraiser.ts +742 -0
  2. package/TradingVault.ts +863 -0
  3. package/abis/SeedRoundFundraiser.json +1519 -0
  4. package/abis/TradingVault.json +1647 -0
  5. package/common.ts +131 -0
  6. package/contracts/SeedRoundFundraiser.ts +1670 -0
  7. package/contracts/TradingVault.ts +1752 -0
  8. package/contracts/common.ts +131 -0
  9. package/contracts/factories/SeedRoundFundraiser__factory.ts +1495 -0
  10. package/contracts/factories/index.ts +4 -0
  11. package/contracts/index.ts +6 -0
  12. package/contracts.json +28 -0
  13. package/dist/SeedRoundFundraiser.d.ts +130 -0
  14. package/dist/SeedRoundFundraiser.js +445 -0
  15. package/dist/TradingVault.d.ts +175 -0
  16. package/dist/TradingVault.js +521 -0
  17. package/dist/abis/SeedRoundFundraiser.json +1519 -0
  18. package/dist/abis/TradingVault.json +1647 -0
  19. package/dist/common.d.ts +50 -0
  20. package/dist/common.js +2 -0
  21. package/dist/contracts/SeedRoundFundraiser.d.ts +936 -0
  22. package/dist/contracts/SeedRoundFundraiser.js +2 -0
  23. package/dist/contracts/TradingVault.d.ts +930 -0
  24. package/dist/contracts/TradingVault.js +2 -0
  25. package/dist/contracts.json +28 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.js +19 -0
  28. package/dist/types.d.ts +291 -0
  29. package/dist/types.js +2 -0
  30. package/dist/utils.d.ts +95 -0
  31. package/dist/utils.js +370 -0
  32. package/dist/whitelist-tokens.json +14 -0
  33. package/index.ts +3 -0
  34. package/package.json +21 -0
  35. package/temp/aiia-vault-sdk-1.0.0.tgz +0 -0
  36. package/tsconfig.json +15 -0
  37. package/types.ts +301 -0
  38. package/utils.ts +576 -0
  39. package/whitelist-tokens.json +14 -0
@@ -0,0 +1,863 @@
1
+ import { ethers } from "ethers";
2
+ import { TradingVault } from "./contracts/TradingVault";
3
+ import TradingVaultABI from "./abis/TradingVault.json";
4
+ import contracts from "./contracts.json";
5
+ import { AnyEvent, AnyEventRaw, EventTypes } from "./types";
6
+ import { type SendTransactionMutateAsync } from "@wagmi/core/query";
7
+ import { Config } from "@wagmi/core/dist/types/createConfig";
8
+ import {
9
+ getRandomProvider,
10
+ checkRpcHealth,
11
+ getTransactionStatus,
12
+ getTokenDecimals,
13
+ getTokenAllowance,
14
+ buildApproveERC20Tx,
15
+ getRewardWeight,
16
+ resolveContractAddress,
17
+ streamEvents,
18
+ getAllEvents,
19
+ signAndSendTransaction,
20
+ } from "./utils";
21
+
22
+ export class TradingVaultSDK {
23
+ name = "TradingVault";
24
+ private contract: TradingVault;
25
+ private providers: ethers.Provider[];
26
+ private _currency: string | null = null;
27
+ private _rewardToken: string | null = null;
28
+ private _currencyDecimals: number | null = null;
29
+ private _rewardTokenDecimals: number | null = null;
30
+ private priceDecimals: number = 6;
31
+ private contractAddress: string;
32
+ private isBootstrapped: boolean = false;
33
+ private configs: Array<{ weight: number; duration: number }> = [];
34
+ private BASE_WEIGHT = 10_000;
35
+ private BASE_PRICE = 100;
36
+
37
+ constructor(rpcUrls: string | string[], contractAddress?: string) {
38
+ // Convert single RPC to array if needed
39
+ const rpcArray = Array.isArray(rpcUrls) ? rpcUrls : [rpcUrls];
40
+
41
+ // Create providers for each RPC
42
+ this.providers = rpcArray.map((rpc) => new ethers.JsonRpcProvider(rpc));
43
+
44
+ // Get a random provider for initial setup
45
+ const initialProvider = getRandomProvider(this.providers);
46
+
47
+ // Determine contract address based on RPC URL if not provided
48
+ this.contractAddress = resolveContractAddress(
49
+ rpcArray[0],
50
+ this.name,
51
+ contracts,
52
+ contractAddress
53
+ );
54
+
55
+ // Initialize contract with initial provider
56
+ this.contract = new ethers.Contract(
57
+ this.contractAddress,
58
+ TradingVaultABI.abi,
59
+ initialProvider
60
+ ) as unknown as TradingVault;
61
+ }
62
+
63
+ private getRandomProvider(): ethers.Provider {
64
+ return getRandomProvider(this.providers);
65
+ }
66
+
67
+ private getContractWithRandomProvider(): TradingVault {
68
+ return new ethers.Contract(
69
+ this.contractAddress,
70
+ TradingVaultABI.abi,
71
+ this.getRandomProvider()
72
+ ) as unknown as TradingVault;
73
+ }
74
+
75
+ async bootstrap() {
76
+ if (this.isBootstrapped) {
77
+ return;
78
+ }
79
+
80
+ // Check health of all RPCs and remove inactive ones
81
+ const healthChecks = await Promise.all(
82
+ this.providers.map((provider, index) => checkRpcHealth(provider, index))
83
+ );
84
+
85
+ // Filter out inactive providers
86
+ this.providers = this.providers.filter((_, index) => healthChecks[index]);
87
+
88
+ if (this.providers.length === 0) {
89
+ throw new Error("No active RPC providers available");
90
+ }
91
+
92
+ await Promise.all([
93
+ this.getCurrencyDecimals(),
94
+ this.getRewardTokenDecimals(),
95
+ (async () => {
96
+ const configLength = await this.getRewardConfigsLength();
97
+ const configPromises = [];
98
+ for (let i = 0; i < configLength; i++) {
99
+ configPromises.push(this.getRewardConfig(i));
100
+ }
101
+ const configs = await Promise.all(configPromises);
102
+ this.configs = configs.map((config) => ({
103
+ weight: Number(config.weight) / this.BASE_WEIGHT,
104
+ duration: Number(config.duration),
105
+ }));
106
+ })(),
107
+ ]);
108
+ this.isBootstrapped = true;
109
+ }
110
+
111
+ async signAndSendTransaction(
112
+ tx: ethers.ContractTransaction,
113
+ wallet: ethers.Wallet | SendTransactionMutateAsync<Config, any>,
114
+ callbacks: {
115
+ onSubmit?: (tx: string) => void | Promise<void>;
116
+ onFinally?: (status: {
117
+ status: boolean | null;
118
+ confirmations: number;
119
+ txHash: string;
120
+ isCompleted: boolean;
121
+ attempts: number;
122
+ }) => void | Promise<void>;
123
+ onError?: (error: Error) => void | Promise<void>;
124
+ } = {}
125
+ ): Promise<{
126
+ transaction: {
127
+ hash: string;
128
+ };
129
+ status: {
130
+ status: boolean | null;
131
+ confirmations: number;
132
+ isCompleted: boolean;
133
+ attempts: number;
134
+ };
135
+ }> {
136
+ return signAndSendTransaction(
137
+ tx,
138
+ wallet,
139
+ () => this.getRandomProvider(),
140
+ callbacks
141
+ );
142
+ }
143
+
144
+ // Transaction builders
145
+ async buildCreatePositionTx(amount: number) {
146
+ const decimals = await this.getCurrencyDecimals();
147
+ const amountBigInt = BigInt(Math.floor(amount * Math.pow(10, decimals)));
148
+ return this.buildCreatePositionTxRaw(amountBigInt);
149
+ }
150
+
151
+ async buildCreatePositionForUserTx(user: string, amount: number) {
152
+ const decimals = await this.getCurrencyDecimals();
153
+ const amountBigInt = BigInt(Math.floor(amount * Math.pow(10, decimals)));
154
+ return this.buildCreatePositionForUserTxRaw(user, amountBigInt);
155
+ }
156
+
157
+ async buildReducePositionTx(tokenId: bigint, amount: number) {
158
+ const decimals = await this.getCurrencyDecimals();
159
+ const amountBigInt = BigInt(Math.floor(amount * Math.pow(10, decimals)));
160
+ return this.buildReducePositionTxRaw(tokenId, amountBigInt);
161
+ }
162
+
163
+ async buildReducePositionsTx(tokenIds: bigint[], amounts: number[]) {
164
+ const decimals = await this.getCurrencyDecimals();
165
+ const amountsBigInt = amounts.map((amount) =>
166
+ BigInt(Math.floor(amount * Math.pow(10, decimals)))
167
+ );
168
+ return this.buildReducePositionsTxRaw(tokenIds, amountsBigInt);
169
+ }
170
+
171
+ async buildBorrowCurrencyTx(amount: number) {
172
+ const decimals = await this.getCurrencyDecimals();
173
+ const amountBigInt = BigInt(Math.floor(amount * Math.pow(10, decimals)));
174
+ return this.buildBorrowCurrencyTxRaw(amountBigInt);
175
+ }
176
+
177
+ async buildRepayCurrencyTx(amount: number) {
178
+ const decimals = await this.getCurrencyDecimals();
179
+ const amountBigInt = BigInt(Math.floor(amount * Math.pow(10, decimals)));
180
+ return this.buildRepayCurrencyTxRaw(amountBigInt);
181
+ }
182
+
183
+ async buildSetPriceTx(newPrice: number, rewardAmount: number) {
184
+ const decimals = this.priceDecimals;
185
+ const rewardDecimals = await this.getRewardTokenDecimals();
186
+ const priceBigInt = BigInt(Math.floor(newPrice * Math.pow(10, decimals)));
187
+ const rewardAmountBigInt = BigInt(
188
+ Math.floor(rewardAmount * Math.pow(10, rewardDecimals))
189
+ );
190
+ return this.buildSetPriceTxRaw(priceBigInt, rewardAmountBigInt);
191
+ }
192
+
193
+ async buildClaimERC20Tx(token: string, amount: number) {
194
+ const tokenContract = new ethers.Contract(
195
+ token,
196
+ ["function decimals() view returns (uint8)"],
197
+ this.getRandomProvider()
198
+ );
199
+ const decimals = await tokenContract.decimals();
200
+ const amountBigInt = BigInt(Math.floor(amount * Math.pow(10, decimals)));
201
+ return this.buildClaimERC20TxRaw(token, amountBigInt);
202
+ }
203
+
204
+ async buildCreatePositionTxRaw(amount: bigint) {
205
+ const tx = await this.contract.createPosition.populateTransaction(amount);
206
+ return tx;
207
+ }
208
+
209
+ async buildCreatePositionForUserTxRaw(user: string, amount: bigint) {
210
+ const tx = await this.contract.createPositionForUser.populateTransaction(
211
+ user,
212
+ amount
213
+ );
214
+ return tx;
215
+ }
216
+
217
+ async buildReducePositionTxRaw(tokenId: bigint, amount: bigint) {
218
+ const tx = await this.contract.reducePosition.populateTransaction(
219
+ tokenId,
220
+ amount
221
+ );
222
+ return tx;
223
+ }
224
+
225
+ async buildReducePositionsTxRaw(tokenIds: bigint[], amounts: bigint[]) {
226
+ const tx = await this.contract.reducePositions.populateTransaction(
227
+ tokenIds,
228
+ amounts
229
+ );
230
+ return tx;
231
+ }
232
+
233
+ async buildBorrowCurrencyTxRaw(amount: bigint) {
234
+ const tx = await this.contract.borrowCurrency.populateTransaction(amount);
235
+ return tx;
236
+ }
237
+
238
+ async buildRepayCurrencyTxRaw(amount: bigint) {
239
+ const tx = await this.contract.repayCurrency.populateTransaction(amount);
240
+ return tx;
241
+ }
242
+
243
+ async buildSetPriceTxRaw(newPrice: bigint, rewardAmount: bigint) {
244
+ const tx = await this.contract.setPrice.populateTransaction(
245
+ newPrice,
246
+ rewardAmount
247
+ );
248
+ return tx;
249
+ }
250
+
251
+ async buildSetCurrencyTxRaw(currency: string) {
252
+ this._currency = null; // Reset cache when currency is about to change
253
+ const tx = await this.contract.setCurrency.populateTransaction(currency);
254
+ return tx;
255
+ }
256
+
257
+ async buildSetRewardTokenTxRaw(rewardToken: string) {
258
+ this._rewardToken = null; // Reset cache when reward token is about to change
259
+ const tx = await this.contract.setRewardToken.populateTransaction(
260
+ rewardToken
261
+ );
262
+ return tx;
263
+ }
264
+
265
+ async buildSetTreasuryTxRaw(treasury: string) {
266
+ const tx = await this.contract.setTreasury.populateTransaction(treasury);
267
+ return tx;
268
+ }
269
+
270
+ async buildUpdateRewardConfigsTxRaw(
271
+ configs: TradingVault.RewardConfigStruct[]
272
+ ) {
273
+ const tx = await this.contract.updateRewardConfigs.populateTransaction(
274
+ configs
275
+ );
276
+ return tx;
277
+ }
278
+
279
+ async buildSetReduceEnabledTxRaw(enabled: boolean) {
280
+ const tx = await this.contract.setReduceEnabled.populateTransaction(
281
+ enabled
282
+ );
283
+ return tx;
284
+ }
285
+
286
+ async buildClaimERC20TxRaw(token: string, amount: bigint) {
287
+ const tx = await this.contract.claimERC20.populateTransaction(
288
+ token,
289
+ amount
290
+ );
291
+ return tx;
292
+ }
293
+
294
+ async buildApproveERC20Tx(
295
+ tokenAddress: string,
296
+ spender: string,
297
+ amount: number
298
+ ) {
299
+ const tokenContract = new ethers.Contract(
300
+ tokenAddress,
301
+ ["function decimals() view returns (uint8)"],
302
+ this.getRandomProvider()
303
+ );
304
+ const decimals = await tokenContract.decimals();
305
+ const amountBigInt = BigInt(
306
+ Math.floor(amount * Math.pow(10, Number(decimals)))
307
+ );
308
+ return this.buildApproveERC20TxRaw(tokenAddress, spender, amountBigInt);
309
+ }
310
+
311
+ async buildApproveERC20TxRaw(
312
+ tokenAddress: string,
313
+ spender: string,
314
+ amount: bigint
315
+ ) {
316
+ return await buildApproveERC20Tx(
317
+ tokenAddress,
318
+ spender,
319
+ amount,
320
+ this.getRandomProvider()
321
+ );
322
+ }
323
+
324
+ async getAllowanceRaw(
325
+ tokenAddress: string,
326
+ owner: string,
327
+ spender: string
328
+ ): Promise<bigint> {
329
+ return await getTokenAllowance(
330
+ tokenAddress,
331
+ owner,
332
+ spender,
333
+ this.getRandomProvider()
334
+ );
335
+ }
336
+
337
+ async getAllowance(
338
+ tokenAddress: string,
339
+ owner: string,
340
+ spender: string
341
+ ): Promise<number> {
342
+ const [allowanceRaw, decimals] = await Promise.all([
343
+ this.getAllowanceRaw(tokenAddress, owner, spender),
344
+ getTokenDecimals(tokenAddress, this.getRandomProvider()),
345
+ ]);
346
+ return Number(allowanceRaw) / Math.pow(10, decimals);
347
+ }
348
+
349
+ async buildGrantOperatorRoleTxRaw(operator: string) {
350
+ const tx = await this.contract.grantOperatorRole.populateTransaction(
351
+ operator
352
+ );
353
+ return tx;
354
+ }
355
+
356
+ // Read methods
357
+ async getPrice(): Promise<number> {
358
+ const priceRaw = await this.getPriceRaw();
359
+ const decimals = this.priceDecimals;
360
+ return Number(priceRaw) / Math.pow(10, decimals);
361
+ }
362
+
363
+ async getPriceRaw(): Promise<bigint> {
364
+ return await this.contract.price();
365
+ }
366
+
367
+ async getCurrency(): Promise<string> {
368
+ if (!this._currency) {
369
+ this._currency = await this.contract.currency();
370
+ }
371
+ return this._currency;
372
+ }
373
+
374
+ async getRewardToken(): Promise<string> {
375
+ if (!this._rewardToken) {
376
+ this._rewardToken = await this.contract.rewardToken();
377
+ }
378
+ return this._rewardToken;
379
+ }
380
+
381
+ async getTreasury(): Promise<string> {
382
+ return await this.contract.treasury();
383
+ }
384
+
385
+ async getTotalAmount(): Promise<number> {
386
+ const totalAmountRaw = await this.getTotalAmountRaw();
387
+ const decimals = await this.getCurrencyDecimals();
388
+ return Number(totalAmountRaw) / Math.pow(10, decimals);
389
+ }
390
+
391
+ async getTotalAmountRaw(): Promise<bigint> {
392
+ return await this.contract.totalAmount();
393
+ }
394
+
395
+ async getTotalBorrowed(): Promise<number> {
396
+ const totalBorrowedRaw = await this.getTotalBorrowedRaw();
397
+ const decimals = await this.getCurrencyDecimals();
398
+ return Number(totalBorrowedRaw) / Math.pow(10, decimals);
399
+ }
400
+
401
+ async getTotalBorrowedRaw(): Promise<bigint> {
402
+ return await this.contract.totalBorrowed();
403
+ }
404
+
405
+ async isReduceEnabled(): Promise<boolean> {
406
+ return await this.contract.isReduceEnabled();
407
+ }
408
+
409
+ async getPosition(tokenId: bigint): Promise<{
410
+ remainingAmount: number;
411
+ entryPrice: number;
412
+ outPrice: number;
413
+ openedAt: number;
414
+ closedAt: number;
415
+ rewardedAmount: number;
416
+ lossAmount: number;
417
+ initAmount: number;
418
+ }> {
419
+ const positionRaw = await this.getPositionRaw(tokenId);
420
+ const currencyDecimals = await this.getCurrencyDecimals();
421
+ const rewardTokenDecimals = await this.getRewardTokenDecimals();
422
+ const priceDecimals = this.priceDecimals;
423
+ return {
424
+ remainingAmount:
425
+ Number(positionRaw.remainingAmount) / Math.pow(10, currencyDecimals),
426
+ entryPrice: Number(positionRaw.entryPrice) / Math.pow(10, priceDecimals),
427
+ outPrice: Number(positionRaw.outPrice) / Math.pow(10, priceDecimals),
428
+ openedAt: Number(positionRaw.openedAt),
429
+ closedAt: Number(positionRaw.closedAt),
430
+ rewardedAmount:
431
+ Number(positionRaw.rewardedAmount) / Math.pow(10, rewardTokenDecimals),
432
+ lossAmount:
433
+ Number(positionRaw.lossAmount) / Math.pow(10, currencyDecimals),
434
+ initAmount:
435
+ Number(positionRaw.initAmount) / Math.pow(10, currencyDecimals),
436
+ };
437
+ }
438
+
439
+ async getPositionRaw(tokenId: bigint) {
440
+ return await this.contract.positions(tokenId);
441
+ }
442
+
443
+ async getRewardConfig(index: number) {
444
+ return await this.contract.rewardConfigs(index);
445
+ }
446
+
447
+ async getRewardConfigsLength(): Promise<bigint> {
448
+ return await this.contract.getRewardConfigsLength();
449
+ }
450
+
451
+ async getCurrencyDecimals(): Promise<number> {
452
+ if (this._currencyDecimals === null) {
453
+ const currency = await this.getCurrency();
454
+ const currencyContract = new ethers.Contract(
455
+ currency,
456
+ ["function decimals() view returns (uint8)"],
457
+ this.getRandomProvider()
458
+ );
459
+ this._currencyDecimals = Number(await currencyContract.decimals());
460
+ }
461
+ return this._currencyDecimals!;
462
+ }
463
+
464
+ async getRewardTokenDecimals(): Promise<number> {
465
+ if (this._rewardTokenDecimals === null) {
466
+ const rewardToken = await this.getRewardToken();
467
+ const rewardTokenContract = new ethers.Contract(
468
+ rewardToken,
469
+ ["function decimals() view returns (uint8)"],
470
+ this.getRandomProvider()
471
+ );
472
+ this._rewardTokenDecimals = Number(await rewardTokenContract.decimals());
473
+ }
474
+ return this._rewardTokenDecimals!;
475
+ }
476
+
477
+ getContractAddress(): string {
478
+ return this.contractAddress;
479
+ }
480
+
481
+ getConfigs(): Array<{ weight: number; duration: number }> {
482
+ return [...this.configs];
483
+ }
484
+
485
+ async getAllEvents(
486
+ fromBlock: number,
487
+ toBlock: number
488
+ ): Promise<AnyEventRaw[]> {
489
+ await this.bootstrap();
490
+ return getAllEvents<AnyEventRaw, TradingVault>(
491
+ this.contract,
492
+ () => this.getRandomProvider(),
493
+ () => this.getContractWithRandomProvider(),
494
+ fromBlock,
495
+ toBlock
496
+ );
497
+ }
498
+
499
+ async streamEvents(
500
+ fromBlock: number,
501
+ onEvent: (event: AnyEvent) => Promise<void>,
502
+ saveLatestBlock: (blockNumber: number) => Promise<void>,
503
+ batchSize: number = 1000,
504
+ sleepTime: number = 5000
505
+ ) {
506
+ await this.bootstrap();
507
+
508
+ return streamEvents<AnyEventRaw, AnyEvent>({
509
+ getProvider: () => this.getRandomProvider(),
510
+ getAllEvents: (fromBlock, toBlock) =>
511
+ this.getAllEvents(fromBlock, toBlock),
512
+ formatEvent: (event) => this.formatEventArgs(event),
513
+ onEvent,
514
+ saveLatestBlock,
515
+ fromBlock,
516
+ batchSize,
517
+ sleepTime,
518
+ });
519
+ }
520
+
521
+ formatEventArgs = (event: AnyEventRaw): AnyEvent => {
522
+ const currencyDecimals = this._currencyDecimals!;
523
+ const rewardTokenDecimals = this._rewardTokenDecimals!;
524
+ const { eventName, args } = event;
525
+
526
+ switch (eventName) {
527
+ case "PositionCreated":
528
+ return {
529
+ ...event,
530
+ args: {
531
+ ...args,
532
+ user: args.user.toLowerCase(),
533
+ amount: Number(args.amount) / Math.pow(10, currencyDecimals),
534
+ entryPrice:
535
+ Number(args.entryPrice) / Math.pow(10, this.priceDecimals),
536
+ tokenId: Number(args.tokenId),
537
+ openedAt: Number(args.openedAt),
538
+ },
539
+ };
540
+
541
+ case "PositionReduced":
542
+ return {
543
+ ...event,
544
+ args: {
545
+ ...args,
546
+ user: args.user.toLowerCase(),
547
+ reducedAmount:
548
+ Number(args.reducedAmount) / Math.pow(10, currencyDecimals),
549
+ remainingAmount:
550
+ Number(args.remainingAmount) / Math.pow(10, currencyDecimals),
551
+ totalReward:
552
+ Number(args.totalReward) / Math.pow(10, rewardTokenDecimals),
553
+ userReward:
554
+ Number(args.userReward) / Math.pow(10, rewardTokenDecimals),
555
+ treasuryReward:
556
+ Number(args.treasuryReward) / Math.pow(10, rewardTokenDecimals),
557
+ rewardedAmount:
558
+ Number(args.rewardedAmount) / Math.pow(10, rewardTokenDecimals),
559
+ weight: Number(args.weight),
560
+ tokenId: Number(args.tokenId),
561
+ lossAmount:
562
+ Number(args.lossAmount) / Math.pow(10, currencyDecimals),
563
+ price: Number(args.price) / Math.pow(10, this.priceDecimals),
564
+ loss: Number(args.loss) / Math.pow(10, currencyDecimals),
565
+ },
566
+ };
567
+
568
+ case "CurrencyBorrowed":
569
+ case "CurrencyRepaid":
570
+ return {
571
+ ...event,
572
+ args: {
573
+ ...args,
574
+ amount: Number(args.amount) / Math.pow(10, currencyDecimals),
575
+ borrower: args.borrower.toLowerCase(),
576
+ },
577
+ };
578
+
579
+ case "PriceUpdated":
580
+ return {
581
+ ...event,
582
+ args: {
583
+ ...args,
584
+ oldPrice: Number(args.oldPrice) / Math.pow(10, this.priceDecimals),
585
+ newPrice: Number(args.newPrice) / Math.pow(10, this.priceDecimals),
586
+ requiredReward:
587
+ Number(args.requiredReward) / Math.pow(10, rewardTokenDecimals),
588
+ },
589
+ };
590
+
591
+ case "TotalAmountUpdated":
592
+ return {
593
+ ...event,
594
+ args: {
595
+ ...args,
596
+ newTotalAmount:
597
+ Number(args.newTotalAmount) / Math.pow(10, currencyDecimals),
598
+ },
599
+ };
600
+
601
+ case "CurrencyUpdated":
602
+ return {
603
+ ...event,
604
+ args: {
605
+ ...args,
606
+ oldCurrency: args.oldCurrency.toLowerCase(),
607
+ newCurrency: args.newCurrency.toLowerCase(),
608
+ },
609
+ };
610
+
611
+ case "RewardTokenUpdated":
612
+ return {
613
+ ...event,
614
+ args: {
615
+ ...args,
616
+ oldRewardToken: args.oldRewardToken.toLowerCase(),
617
+ newRewardToken: args.newRewardToken.toLowerCase(),
618
+ },
619
+ };
620
+
621
+ case "ReduceEnabledUpdated":
622
+ return {
623
+ ...event,
624
+ args: {
625
+ ...args,
626
+ enabled: args.enabled,
627
+ },
628
+ };
629
+
630
+ case "TreasuryUpdated":
631
+ return {
632
+ ...event,
633
+ args: {
634
+ ...args,
635
+ oldTreasury: args.oldTreasury.toLowerCase(),
636
+ newTreasury: args.newTreasury.toLowerCase(),
637
+ },
638
+ };
639
+
640
+ case "RewardConfigsUpdated":
641
+ return {
642
+ ...event,
643
+ args: {
644
+ ...args,
645
+ },
646
+ };
647
+
648
+ case "TotalRewardsUpdated": {
649
+ return {
650
+ ...event,
651
+ args: {
652
+ ...args,
653
+ oldAmount:
654
+ Number(args.oldAmount) / Math.pow(10, rewardTokenDecimals),
655
+ newAmount:
656
+ Number(args.newAmount) / Math.pow(10, rewardTokenDecimals),
657
+ },
658
+ };
659
+ }
660
+
661
+ case "TotalRewardsHarvestedUpdated": {
662
+ return {
663
+ ...event,
664
+ args: {
665
+ ...args,
666
+ oldAmount:
667
+ Number(args.oldAmount) / Math.pow(10, rewardTokenDecimals),
668
+ newAmount:
669
+ Number(args.newAmount) / Math.pow(10, rewardTokenDecimals),
670
+ },
671
+ };
672
+ }
673
+
674
+ default:
675
+ return event; // Return unformatted args for unknown events
676
+ }
677
+ };
678
+
679
+ estimateUnrealizedPnl({
680
+ price,
681
+ entryPrice,
682
+ initAmount,
683
+ remainingAmount,
684
+ }: {
685
+ price: number;
686
+ entryPrice: number;
687
+ initAmount: number;
688
+ remainingAmount: number;
689
+ }): {
690
+ pnl: number;
691
+ pnlPercentage: number;
692
+ } {
693
+ // Calculate PNL using the formula: amount * (price - entryPrice) / BASE_PRICE + rewardedAmount - lossAmount
694
+ const pnl = (remainingAmount * (price - entryPrice)) / this.BASE_PRICE;
695
+
696
+ // Calculate PNL percentage relative to the initial amount
697
+ const pnlPercentage = (pnl / initAmount) * 100;
698
+
699
+ return {
700
+ pnl,
701
+ pnlPercentage,
702
+ };
703
+ }
704
+
705
+ estReducePosition({
706
+ amount,
707
+ price,
708
+ entryPrice,
709
+ openedAt,
710
+ configs = [],
711
+ }: {
712
+ amount: number;
713
+ price: number;
714
+ entryPrice: number;
715
+ openedAt: number;
716
+ configs: Array<{ weight: number; duration: number }>;
717
+ }): {
718
+ amount: number;
719
+ totalReward: number;
720
+ userReward: number;
721
+ treasuryReward: number;
722
+ weight: number;
723
+ } {
724
+ const BASE_PRICE = this.BASE_PRICE;
725
+ const BASE_WEIGHT = this.BASE_WEIGHT;
726
+ let returnAmount = amount;
727
+ let totalReward = 0;
728
+ let userReward = 0;
729
+ let treasuryReward = 0;
730
+ let weight = 0;
731
+
732
+ // If price is less than entry price, user is at a loss
733
+ if (price < entryPrice) {
734
+ // Calculate loss amount
735
+ const priceDiff = entryPrice - price;
736
+ const loss = (amount * priceDiff) / 100;
737
+ returnAmount = amount - loss;
738
+
739
+ // No rewards when at a loss
740
+ return {
741
+ amount: returnAmount,
742
+ totalReward: 0,
743
+ userReward: 0,
744
+ treasuryReward: 0,
745
+ weight: 0,
746
+ };
747
+ }
748
+
749
+ // Calculate rewards if price is higher than entry price
750
+ if (price >= entryPrice) {
751
+ totalReward = (amount * (price - entryPrice)) / BASE_PRICE;
752
+
753
+ // Get weight based on duration
754
+ const duration = Math.floor(Date.now() / 1000) - openedAt;
755
+ weight = this.getRewardWeight(duration, configs);
756
+
757
+ // Calculate user and treasury rewards based on weight
758
+ userReward = totalReward * weight;
759
+ treasuryReward = totalReward - userReward;
760
+ }
761
+
762
+ return {
763
+ amount: returnAmount,
764
+ totalReward,
765
+ userReward,
766
+ treasuryReward,
767
+ weight,
768
+ };
769
+ }
770
+
771
+ estReducePositions({
772
+ positions,
773
+ price,
774
+ configs = [],
775
+ }: {
776
+ positions: Array<{
777
+ amount: number;
778
+ entryPrice: number;
779
+ openedAt: number;
780
+ }>;
781
+ price: number;
782
+ configs: Array<{ weight: number; duration: number }>;
783
+ }): {
784
+ amount: number;
785
+ totalReward: number;
786
+ userReward: number;
787
+ treasuryReward: number;
788
+ } {
789
+ const results = positions.map((position) =>
790
+ this.estReducePosition({
791
+ amount: position.amount,
792
+ price,
793
+ entryPrice: position.entryPrice,
794
+ openedAt: position.openedAt,
795
+ configs,
796
+ })
797
+ );
798
+
799
+ return results.reduce(
800
+ (acc, curr) => ({
801
+ amount: acc.amount + curr.amount,
802
+ totalReward: acc.totalReward + curr.totalReward,
803
+ userReward: acc.userReward + curr.userReward,
804
+ treasuryReward: acc.treasuryReward + curr.treasuryReward,
805
+ }),
806
+ { amount: 0, totalReward: 0, userReward: 0, treasuryReward: 0 }
807
+ );
808
+ }
809
+
810
+ private getRewardWeight(
811
+ duration: number,
812
+ configs: Array<{ weight: number; duration: number }>
813
+ ): number {
814
+ return getRewardWeight(duration, configs);
815
+ }
816
+
817
+ estimateUnrealizedPnls({
818
+ price,
819
+ positions,
820
+ }: {
821
+ price: number;
822
+ positions: Array<{
823
+ entryPrice: number;
824
+ initAmount: number;
825
+ remainingAmount: number;
826
+ rewardedAmount: number;
827
+ lossAmount: number;
828
+ }>;
829
+ }): {
830
+ totalInitAmount: number;
831
+ totalRemainingAmount: number;
832
+ totalRewardedAmount: number;
833
+ totalLossAmount: number;
834
+ totalPnl: number;
835
+ } {
836
+ return positions.reduce(
837
+ (acc, position) => {
838
+ const { pnl } = this.estimateUnrealizedPnl({
839
+ price,
840
+ ...position,
841
+ });
842
+
843
+ return {
844
+ totalInitAmount: acc.totalInitAmount + position.initAmount,
845
+ totalRemainingAmount:
846
+ acc.totalRemainingAmount + position.remainingAmount,
847
+ totalRewardedAmount:
848
+ acc.totalRewardedAmount + position.rewardedAmount,
849
+ totalLossAmount: acc.totalLossAmount + position.lossAmount,
850
+ totalPnl: acc.totalPnl + pnl,
851
+ };
852
+ },
853
+ {
854
+ totalInitAmount: 0,
855
+ totalRemainingAmount: 0,
856
+ totalRewardedAmount: 0,
857
+ totalLossAmount: 0,
858
+ totalPnl: 0,
859
+ }
860
+ );
861
+ }
862
+ }
863
+