@scallop-io/sui-scallop-sdk 2.3.2 → 2.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scallop-io/sui-scallop-sdk",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "Typescript sdk for interacting with Scallop contract on SUI",
5
5
  "keywords": [
6
6
  "sui",
@@ -74,10 +74,16 @@ export const queryKeys = {
74
74
  },
75
75
  },
76
76
  oracle: {
77
- getPythLatestPriceFeeds: (priceIds: string[]) => [
77
+ getPythLatestPriceFeeds: (priceIds: string[], endpoint: string) => [
78
78
  'oracle',
79
79
  'getPythPriceIds',
80
80
  priceIds,
81
+ endpoint,
82
+ ],
83
+ getCoinPrices: (priceIds: string[]) => [
84
+ 'oracle',
85
+ 'getCoinPrices',
86
+ priceIds,
81
87
  ],
82
88
  },
83
89
  };
@@ -60,7 +60,7 @@ import {
60
60
  queryObligation,
61
61
  queryVeScaKeyIdFromReferralBindings,
62
62
  } from 'src/queries';
63
- import { SuiObjectRef, SuiObjectData } from '@mysten/sui/dist/cjs/client';
63
+ import { SuiObjectRef, SuiObjectData } from '@mysten/sui/client';
64
64
  import { SuiObjectArg } from '@scallop-io/sui-kit';
65
65
  import { ScallopQueryInterface } from './interface';
66
66
 
@@ -26,6 +26,7 @@ import { QueryKey } from '@tanstack/query-core';
26
26
  import ScallopQueryClient, {
27
27
  ScallopQueryClientParams,
28
28
  } from './scallopQueryClient';
29
+ import { RateLimiter } from './rateLimiter';
29
30
 
30
31
  type QueryInspectTxnParams = {
31
32
  queryTarget: string;
@@ -65,62 +66,6 @@ const deepMergeObject = <T>(curr: T, update: T): T => {
65
66
  return result;
66
67
  };
67
68
 
68
- export class RateLimiter {
69
- private tokens: number;
70
- private lastRefillTime: number;
71
- private readonly refillRate: number; // tokens per millisecond
72
-
73
- constructor(private readonly capacity: number = 10) {
74
- this.refillRate = this.capacity / 1000; // 10 tokens per second = 0.01 tokens/ms
75
- this.tokens = this.capacity;
76
- this.lastRefillTime = Date.now();
77
- }
78
-
79
- private refill() {
80
- const now = Date.now();
81
- const elapsed = now - this.lastRefillTime;
82
- const newTokens = elapsed * this.refillRate;
83
-
84
- this.tokens = Math.min(this.capacity, this.tokens + newTokens);
85
- this.lastRefillTime = now;
86
- }
87
-
88
- private getTimeToNextToken(): number {
89
- this.refill();
90
-
91
- if (this.tokens >= 1) {
92
- return 0;
93
- }
94
-
95
- // Calculate exact milliseconds needed for 1 full token
96
- const deficit = 1 - this.tokens;
97
- return Math.ceil(deficit / this.refillRate);
98
- }
99
-
100
- async acquireToken(): Promise<void> {
101
- // eslint-disable-next-line no-constant-condition
102
- while (true) {
103
- const waitTime = this.getTimeToNextToken();
104
-
105
- if (waitTime === 0) {
106
- if (this.tokens >= 1) {
107
- this.tokens -= 1;
108
- return;
109
- }
110
- continue;
111
- }
112
-
113
- await new Promise((resolve) => setTimeout(resolve, waitTime));
114
- this.refill();
115
- }
116
- }
117
-
118
- async execute<T>(fn: () => Promise<T>): Promise<T> {
119
- await this.acquireToken();
120
- return await fn();
121
- }
122
- }
123
-
124
69
  class ScallopSuiKit extends ScallopQueryClient {
125
70
  public readonly suiKit: SuiKit;
126
71
  private _walletAddress: string;
@@ -16,7 +16,7 @@ import {
16
16
  } from 'src/constants';
17
17
  import { PriceFeed, SuiPriceServiceConnection } from '@pythnetwork/pyth-sui-js';
18
18
  import ScallopSuiKit, { ScallopSuiKitParams } from './scallopSuiKit';
19
- import { SuiObjectData } from '@mysten/sui/dist/cjs/client';
19
+ import { SuiObjectData } from '@mysten/sui/client';
20
20
  import { queryObligation } from 'src/queries';
21
21
  import { ScallopUtilsInterface } from './interface';
22
22
 
@@ -521,25 +521,24 @@ class ScallopUtils implements ScallopUtilsInterface {
521
521
  ]),
522
522
  ] as string[]
523
523
  ) {
524
- let coinPrices: CoinPrices = {};
524
+ const priceIdsMap = new Map(
525
+ coinNames.map((coinName) => [
526
+ coinName,
527
+ this.address.get(`core.coins.${coinName}.oracle.pyth.feed`),
528
+ ])
529
+ );
525
530
 
526
- const endpoints = this.pythEndpoints;
527
- const failedRequests: Set<string> = new Set(coinNames);
528
-
529
- for (const endpoint of endpoints) {
530
- const priceIdPairs = Array.from(failedRequests.values()).reduce(
531
- (acc, coinName) => {
532
- const priceId =
533
- this.address.get(`core.coins.${coinName}.oracle.pyth.feed`) ??
534
- this.constants.poolAddresses[coinName]?.pythFeed;
535
- acc.push([coinName, priceId]);
536
- return acc;
537
- },
538
- [] as [string, string][]
539
- );
540
- if (priceIdPairs.length === 0) break;
531
+ const priceIds = Array.from(priceIdsMap.values());
532
+ const state = this.queryClient.getQueryState(
533
+ queryKeys.oracle.getCoinPrices(priceIds)
534
+ );
541
535
 
542
- const priceIds = priceIdPairs.map(([_, priceId]) => priceId);
536
+ if (state && state && Date.now() - (state.dataUpdatedAt ?? 0) < 30_000) {
537
+ return state.data as CoinPrices;
538
+ }
539
+
540
+ let coinPrices: CoinPrices = {};
541
+ for (const endpoint of this.pythEndpoints) {
543
542
  const pythConnection = new SuiPriceServiceConnection(endpoint, {
544
543
  timeout: this.timeout,
545
544
  httpRetries: 0,
@@ -547,33 +546,49 @@ class ScallopUtils implements ScallopUtilsInterface {
547
546
 
548
547
  try {
549
548
  const feeds = await this.queryClient.fetchQuery({
550
- queryKey: queryKeys.oracle.getPythLatestPriceFeeds(priceIds),
549
+ queryKey: queryKeys.oracle.getPythLatestPriceFeeds(
550
+ priceIds,
551
+ endpoint
552
+ ),
551
553
  queryFn: async () => {
552
554
  return await pythConnection.getLatestPriceFeeds(priceIds);
553
555
  },
554
- staleTime: 30000,
555
- gcTime: 30000,
556
+ retry: false,
557
+ staleTime: 30_000,
558
+ gcTime: 30_000,
556
559
  });
557
- if (feeds) {
558
- feeds.forEach((feed, idx) => {
559
- const coinName = priceIdPairs[idx][0] as string;
560
- const data = this.parseDataFromPythPriceFeed(feed);
561
- coinPrices[coinName as string] = data.price;
562
- failedRequests.delete(coinName as string); // remove success price feed to prevent duplicate request on the next endpoint
563
- });
564
- }
560
+ if (!feeds) throw new Error('No feeds returned from pyth');
561
+
562
+ if (feeds.length !== priceIds.length)
563
+ throw new Error('Incomplete feeds returned from pyth');
564
+
565
+ feeds.forEach((feed, idx) => {
566
+ const coinName = coinNames[idx] as string;
567
+ const data = this.parseDataFromPythPriceFeed(feed);
568
+ coinPrices[coinName as string] = data.price;
569
+ });
570
+ this.queryClient.setQueryData(
571
+ queryKeys.oracle.getCoinPrices(priceIds),
572
+ coinPrices
573
+ );
565
574
  } catch (e: any) {
575
+ if ('status' in e && e.status === 403) {
576
+ console.log(`trying next pyth endpoint`);
577
+ continue; // try next endpoint
578
+ }
566
579
  console.error(e.message);
567
580
  }
568
- if (failedRequests.size === 0) break;
569
581
  }
570
582
 
571
- if (failedRequests.size > 0) {
583
+ if (Object.keys(coinPrices).length === 0) {
572
584
  coinPrices = {
573
585
  ...coinPrices,
574
- ...(await this.getPythPrices(Array.from(failedRequests.values()))),
586
+ ...(await this.getPythPrices(Array.from(coinNames))),
575
587
  };
576
- failedRequests.clear();
588
+ this.queryClient.setQueryData(
589
+ queryKeys.oracle.getCoinPrices(priceIds),
590
+ coinPrices
591
+ );
577
592
  }
578
593
 
579
594
  return coinPrices;
@@ -4,7 +4,7 @@ import type {
4
4
  GetOwnedObjectsParams,
5
5
  SuiObjectData,
6
6
  SuiObjectDataOptions,
7
- } from '@mysten/sui/dist/cjs/client';
7
+ } from '@mysten/sui/client';
8
8
  import type { SuiObjectArg, SuiTxArg } from '@scallop-io/sui-kit';
9
9
 
10
10
  export namespace QueryKeys {