@scallop-io/sui-scallop-sdk 2.3.1 → 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.1",
3
+ "version": "2.3.4",
4
4
  "description": "Typescript sdk for interacting with Scallop contract on SUI",
5
5
  "keywords": [
6
6
  "sui",
@@ -95,8 +95,12 @@ export const updatePythPriceFeeds = async (
95
95
  '0xa8b8dcc9880166edb57b53e05f8df7364d31b5d9b7d107fd27f0b69cf338b687',
96
96
  }
97
97
  );
98
- const priceIds = assetCoinNames.map((assetCoinName) =>
99
- builder.address.get(`core.coins.${assetCoinName}.oracle.pyth.feed`)
98
+ const priceIds = Array.from(
99
+ new Set(
100
+ assetCoinNames.map((assetCoinName) =>
101
+ builder.address.get(`core.coins.${assetCoinName}.oracle.pyth.feed`)
102
+ )
103
+ )
100
104
  );
101
105
 
102
106
  const endpoints = builder.utils.pythEndpoints ?? [
@@ -106,7 +110,11 @@ export const updatePythPriceFeeds = async (
106
110
  // iterate through the endpoints
107
111
  for (const endpoint of endpoints) {
108
112
  try {
109
- const pythConnection = new SuiPriceServiceConnection(endpoint);
113
+ const pythConnection = new SuiPriceServiceConnection(endpoint, {
114
+ priceFeedRequestConfig: {
115
+ binary: true,
116
+ },
117
+ });
110
118
  const priceUpdateData =
111
119
  await pythConnection.getPriceFeedsUpdateData(priceIds);
112
120
 
@@ -74,6 +74,16 @@ export const queryKeys = {
74
74
  },
75
75
  },
76
76
  oracle: {
77
- getPythLatestPriceFeeds: () => ['oracle', 'getPythPriceIds'],
77
+ getPythLatestPriceFeeds: (priceIds: string[], endpoint: string) => [
78
+ 'oracle',
79
+ 'getPythPriceIds',
80
+ priceIds,
81
+ endpoint,
82
+ ],
83
+ getCoinPrices: (priceIds: string[]) => [
84
+ 'oracle',
85
+ 'getCoinPrices',
86
+ priceIds,
87
+ ],
78
88
  },
79
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,58 +521,74 @@ 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,
544
+ httpRetries: 0,
545
545
  });
546
546
 
547
547
  try {
548
548
  const feeds = await this.queryClient.fetchQuery({
549
- queryKey: queryKeys.oracle.getPythLatestPriceFeeds(),
549
+ queryKey: queryKeys.oracle.getPythLatestPriceFeeds(
550
+ priceIds,
551
+ endpoint
552
+ ),
550
553
  queryFn: async () => {
551
554
  return await pythConnection.getLatestPriceFeeds(priceIds);
552
555
  },
553
- staleTime: 30000,
554
- gcTime: 30000,
556
+ retry: false,
557
+ staleTime: 30_000,
558
+ gcTime: 30_000,
555
559
  });
556
- if (feeds) {
557
- feeds.forEach((feed, idx) => {
558
- const coinName = priceIdPairs[idx][0] as string;
559
- const data = this.parseDataFromPythPriceFeed(feed);
560
- coinPrices[coinName as string] = data.price;
561
- failedRequests.delete(coinName as string); // remove success price feed to prevent duplicate request on the next endpoint
562
- });
563
- }
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
+ );
564
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
+ }
565
579
  console.error(e.message);
566
580
  }
567
- if (failedRequests.size === 0) break;
568
581
  }
569
582
 
570
- if (failedRequests.size > 0) {
583
+ if (Object.keys(coinPrices).length === 0) {
571
584
  coinPrices = {
572
585
  ...coinPrices,
573
- ...(await this.getPythPrices(Array.from(failedRequests.values()))),
586
+ ...(await this.getPythPrices(Array.from(coinNames))),
574
587
  };
575
- failedRequests.clear();
588
+ this.queryClient.setQueryData(
589
+ queryKeys.oracle.getCoinPrices(priceIds),
590
+ coinPrices
591
+ );
576
592
  }
577
593
 
578
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 {