@scallop-io/sui-scallop-sdk 2.0.11 → 2.0.13-merge-split-ve-sca-alpha.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.
@@ -31,7 +31,6 @@ type QueryInspectTxnParams = {
31
31
  };
32
32
 
33
33
  const DEFAULT_TOKENS_PER_INTERVAL = 10;
34
- const DEFAULT_INTERVAL_IN_MS = 250;
35
34
 
36
35
  const deepMergeObject = <T>(curr: T, update: T): T => {
37
36
  const result = { ...curr }; // Clone the current object to avoid mutation
@@ -56,6 +55,61 @@ const deepMergeObject = <T>(curr: T, update: T): T => {
56
55
  return result;
57
56
  };
58
57
 
58
+ export class RateLimiter {
59
+ private tokens: number;
60
+ private lastRefillTime: number;
61
+ private readonly refillRate: number; // tokens per millisecond
62
+
63
+ constructor(private readonly capacity: number = 10) {
64
+ this.refillRate = this.capacity / 1000; // 10 tokens per second = 0.01 tokens/ms
65
+ this.tokens = this.capacity;
66
+ this.lastRefillTime = Date.now();
67
+ }
68
+
69
+ private refill() {
70
+ const now = Date.now();
71
+ const elapsed = now - this.lastRefillTime;
72
+ const newTokens = elapsed * this.refillRate;
73
+
74
+ this.tokens = Math.min(this.capacity, this.tokens + newTokens);
75
+ this.lastRefillTime = now;
76
+ }
77
+
78
+ private getTimeToNextToken(): number {
79
+ this.refill();
80
+
81
+ if (this.tokens >= 1) {
82
+ return 0;
83
+ }
84
+
85
+ // Calculate exact milliseconds needed for 1 full token
86
+ const deficit = 1 - this.tokens;
87
+ return Math.ceil(deficit / this.refillRate);
88
+ }
89
+
90
+ async acquireToken(): Promise<void> {
91
+ // eslint-disable-next-line no-constant-condition
92
+ while (true) {
93
+ const waitTime = this.getTimeToNextToken();
94
+
95
+ if (waitTime === 0) {
96
+ if (this.tokens >= 1) {
97
+ this.tokens -= 1;
98
+ return;
99
+ }
100
+ continue;
101
+ }
102
+
103
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
104
+ this.refill();
105
+ }
106
+ }
107
+
108
+ async execute<T>(fn: () => Promise<T>): Promise<T> {
109
+ await this.acquireToken();
110
+ return await fn();
111
+ }
112
+ }
59
113
  /**
60
114
  * @description
61
115
  * It provides caching for moveCall, RPC Request, and API Request.
@@ -74,12 +128,8 @@ export class ScallopCache {
74
128
 
75
129
  public queryClient: QueryClient;
76
130
  public suiKit: SuiKit;
77
- // private tokenBucket: TokenBucket;
78
131
  public walletAddress: string;
79
- private tokensPerInterval: number = DEFAULT_TOKENS_PER_INTERVAL;
80
- private interval: number = DEFAULT_INTERVAL_IN_MS;
81
- private tokens: number;
82
- private lastRefill: number;
132
+ private limiter: RateLimiter;
83
133
 
84
134
  public constructor(
85
135
  params: ScallopCacheParams = {},
@@ -101,8 +151,9 @@ export class ScallopCache {
101
151
  // // if(cacheOptions.mutations)this.queryClient.setMutationDefaults(cacheOptions.mutations);
102
152
  // }
103
153
 
104
- this.tokens = this.tokensPerInterval; // Initial tokens value
105
- this.lastRefill = Date.now();
154
+ this.limiter = new RateLimiter(
155
+ this.params.tokensPerSecond ?? DEFAULT_TOKENS_PER_INTERVAL
156
+ );
106
157
  this.walletAddress = params.walletAddress ?? this.suiKit.currentAddress();
107
158
  }
108
159
 
@@ -110,79 +161,6 @@ export class ScallopCache {
110
161
  return this.suiKit.client();
111
162
  }
112
163
 
113
- private refill() {
114
- const now = Date.now();
115
- const elapsed = now - this.lastRefill;
116
-
117
- if (elapsed >= this.interval) {
118
- const tokensToAdd =
119
- Math.floor(elapsed / this.interval) * this.tokensPerInterval;
120
- this.tokens = Math.min(this.tokens + tokensToAdd, this.tokensPerInterval);
121
-
122
- // Update lastRefill to reflect the exact time of the last "refill"
123
- this.lastRefill += Math.floor(elapsed / this.interval) * this.interval;
124
- }
125
- }
126
-
127
- private removeTokens(count: number) {
128
- this.refill();
129
- if (this.tokens >= count) {
130
- this.tokens -= count;
131
- return true;
132
- }
133
- return false;
134
- }
135
-
136
- private async callWithRateLimit<T>(
137
- fn: () => Promise<T>,
138
- maxRetries = 15,
139
- backoffFactor = 1.25 // The factor by which to increase the delay
140
- ): Promise<T | null> {
141
- let retries = 0;
142
-
143
- const tryRequest = async (): Promise<T | null> => {
144
- if (this.removeTokens(1)) {
145
- const result = await fn();
146
- return result;
147
- } else if (retries < maxRetries) {
148
- retries++;
149
- const delay = this.interval * Math.pow(backoffFactor, retries);
150
- // console.error(`Rate limit exceeded, retrying in ${delay} ms`);
151
- await new Promise((resolve) => setTimeout(resolve, delay));
152
- return tryRequest();
153
- } else {
154
- console.error('Maximum retries reached');
155
- return null;
156
- }
157
- };
158
-
159
- return tryRequest();
160
- }
161
-
162
- /**
163
- * @description Invalidate cache based on the refetchType parameter
164
- * @param refetchType Determines the type of queries to be refetched. Defaults to `active`.
165
- *
166
- * - `active`: Only queries that match the refetch predicate and are actively being rendered via useQuery and related functions will be refetched in the background.
167
- * - `inactive`: Only queries that match the refetch predicate and are NOT actively being rendered via useQuery and related functions will be refetched in the background.
168
- * - `all`: All queries that match the refetch predicate will be refetched in the background.
169
- * - `none`: No queries will be refetched. Queries that match the refetch predicate will only be marked as invalid.
170
- */
171
- // public async invalidateAllCache() {
172
- // return Object.values(queryKeys.rpc).map((t) =>
173
- // this.queryClient.invalidateQueries({
174
- // queryKey: t(),
175
- // type: 'all',
176
- // })
177
- // );
178
- // }
179
-
180
- private retryFn(errCount: number, e: any) {
181
- if (errCount === 5) return false;
182
- if (e.status === 429) return true;
183
- return false;
184
- }
185
-
186
164
  /**
187
165
  * @description Provides cache for inspectTxn of the SuiKit.
188
166
  * @param QueryInspectTxnParams
@@ -234,12 +212,10 @@ export class ScallopCache {
234
212
  txBlock.moveCall(queryTarget, resolvedArgs, typeArgs);
235
213
 
236
214
  const query = await this.queryClient.fetchQuery({
237
- retry: this.retryFn,
238
- retryDelay: 1000,
239
215
  queryKey: queryKeys.rpc.getInspectTxn(queryTarget, args, typeArgs),
240
216
  queryFn: async () => {
241
- return await this.callWithRateLimit(
242
- async () => await this.suiKit.inspectTxn(txBlock)
217
+ return await this.limiter.execute(() =>
218
+ this.suiKit.inspectTxn(txBlock)
243
219
  );
244
220
  },
245
221
  });
@@ -251,13 +227,12 @@ export class ScallopCache {
251
227
  return this.queryClient.fetchQuery({
252
228
  queryKey: queryKeys.rpc.getNormalizedMoveFunction(target),
253
229
  queryFn: async () => {
254
- return await this.callWithRateLimit(
255
- async () =>
256
- await this.suiKit.client().getNormalizedMoveFunction({
257
- package: address,
258
- module,
259
- function: name,
260
- })
230
+ return await this.limiter.execute(() =>
231
+ this.client.getNormalizedMoveFunction({
232
+ package: address,
233
+ module,
234
+ function: name,
235
+ })
261
236
  );
262
237
  },
263
238
  });
@@ -280,16 +255,13 @@ export class ScallopCache {
280
255
  showType: true,
281
256
  };
282
257
  return this.queryClient.fetchQuery({
283
- retry: this.retryFn,
284
- retryDelay: 1000,
285
258
  queryKey: queryKeys.rpc.getObject(objectId, options),
286
259
  queryFn: async () => {
287
- return await this.callWithRateLimit(
288
- async () =>
289
- await this.client.getObject({
290
- id: objectId,
291
- options,
292
- })
260
+ return await this.limiter.execute(() =>
261
+ this.client.getObject({
262
+ id: objectId,
263
+ options,
264
+ })
293
265
  );
294
266
  },
295
267
  });
@@ -309,12 +281,10 @@ export class ScallopCache {
309
281
  };
310
282
 
311
283
  return this.queryClient.fetchQuery({
312
- retry: this.retryFn,
313
- retryDelay: 1000,
314
284
  queryKey: queryKeys.rpc.getObjects(objectIds),
315
285
  queryFn: async () => {
316
- const results = await this.callWithRateLimit(
317
- async () => await this.suiKit.getObjects(objectIds, options)
286
+ const results = await this.limiter.execute(() =>
287
+ this.suiKit.getObjects(objectIds, options)
318
288
  );
319
289
  if (results) {
320
290
  results.forEach((result) => {
@@ -347,11 +317,9 @@ export class ScallopCache {
347
317
  public async queryGetOwnedObjects(input: GetOwnedObjectsParams) {
348
318
  // @TODO: This query need its own separate rate limiter (as owned objects can theoretically be infinite), need a better way to handle this
349
319
  return this.queryClient.fetchQuery({
350
- retry: this.retryFn,
351
- retryDelay: 1000,
352
320
  queryKey: queryKeys.rpc.getOwnedObjects(input),
353
321
  queryFn: async () => {
354
- const results = await this.callWithRateLimit(() =>
322
+ const results = await this.limiter.execute(() =>
355
323
  this.client.getOwnedObjects(input)
356
324
  );
357
325
  if (results && results.data.length > 0) {
@@ -388,12 +356,10 @@ export class ScallopCache {
388
356
  input: GetDynamicFieldsParams
389
357
  ): Promise<DynamicFieldPage | null> {
390
358
  return this.queryClient.fetchQuery({
391
- retry: this.retryFn,
392
- retryDelay: 1000,
393
359
  queryKey: queryKeys.rpc.getDynamicFields(input),
394
360
  queryFn: async () => {
395
- return await this.callWithRateLimit(
396
- async () => await this.client.getDynamicFields(input)
361
+ return await this.limiter.execute(() =>
362
+ this.client.getDynamicFields(input)
397
363
  );
398
364
  },
399
365
  });
@@ -403,11 +369,9 @@ export class ScallopCache {
403
369
  input: GetDynamicFieldObjectParams
404
370
  ): Promise<SuiObjectResponse | null> {
405
371
  return this.queryClient.fetchQuery({
406
- retry: this.retryFn,
407
- retryDelay: (attemptIndex) => Math.min(1000 * attemptIndex, 8000),
408
372
  queryKey: queryKeys.rpc.getDynamicFieldObject(input),
409
373
  queryFn: async () => {
410
- const result = await this.callWithRateLimit(() =>
374
+ const result = await this.limiter.execute(() =>
411
375
  this.client.getDynamicFieldObject(input)
412
376
  );
413
377
  if (result?.data) {
@@ -433,12 +397,10 @@ export class ScallopCache {
433
397
  owner: string
434
398
  ): Promise<{ [k: string]: CoinBalance }> {
435
399
  return this.queryClient.fetchQuery({
436
- retry: this.retryFn,
437
- retryDelay: 1000,
438
400
  queryKey: queryKeys.rpc.getAllCoinBalances(owner),
439
401
  queryFn: async () => {
440
- const allBalances = await this.callWithRateLimit(
441
- async () => await this.client.getAllBalances({ owner })
402
+ const allBalances = await this.limiter.execute(() =>
403
+ this.client.getAllBalances({ owner })
442
404
  );
443
405
  if (!allBalances) return {};
444
406
  const balances = allBalances.reduce(
@@ -18,7 +18,7 @@ import type {
18
18
  ScallopTxBlock,
19
19
  ScallopClientVeScaReturnType,
20
20
  ScallopClientInstanceParams,
21
- } from '../types';
21
+ } from 'src/types';
22
22
  import { newSuiKit } from './suiKit';
23
23
  import { ScallopConstants } from './scallopConstants';
24
24
 
@@ -109,6 +109,7 @@ export class ScallopConstants {
109
109
  'spool',
110
110
  'oracles',
111
111
  'pythEndpoints',
112
+ 'emerging',
112
113
  ] as const;
113
114
  return (
114
115
  this.isAddressInitialized && // address is initialized
@@ -387,15 +388,22 @@ export class ScallopConstants {
387
388
  ]);
388
389
 
389
390
  if (!this.params.forceWhitelistInterface) {
390
- this._whitelist = Object.fromEntries(
391
- Object.entries(whitelistResponse)
392
- .filter(([_, value]) => Array.isArray(value) || value instanceof Set)
393
- .map(([key, value]) => [
394
- key as keyof Whitelist,
395
- value instanceof Set ? value : new Set(value),
396
- ])
397
- ) as Whitelist;
391
+ this._whitelist = Object.keys(this._whitelist).reduce(
392
+ (acc, key: unknown) => {
393
+ const whiteListKey = key as keyof Whitelist;
394
+ const whiteListValue = whitelistResponse[whiteListKey];
395
+ acc[whiteListKey] =
396
+ whiteListValue instanceof Set
397
+ ? whiteListValue
398
+ : Array.isArray(whiteListValue)
399
+ ? new Set(whiteListValue)
400
+ : new Set();
401
+ return acc;
402
+ },
403
+ {} as Whitelist
404
+ );
398
405
  }
406
+
399
407
  if (!this.params.forcePoolAddressInterface) {
400
408
  this._poolAddresses = Object.fromEntries(
401
409
  Object.entries(poolAddressesResponse)
@@ -1,5 +1,5 @@
1
1
  import axios, { AxiosInstance } from 'axios';
2
- import { SDK_API_BASE_URL } from '../constants';
2
+ import { SDK_API_BASE_URL } from 'src/constants';
3
3
  import type {
4
4
  Market,
5
5
  MarketPools,
@@ -3,8 +3,8 @@ import {
3
3
  parseOriginBorrowIncentivePoolData,
4
4
  parseOriginBorrowIncentiveAccountData,
5
5
  calculateBorrowIncentivePoolPointData,
6
- } from '../utils';
7
- import type { ScallopAddress, ScallopQuery, ScallopUtils } from '../models';
6
+ } from 'src/utils';
7
+ import type { ScallopAddress, ScallopQuery, ScallopUtils } from 'src/models';
8
8
  import type {
9
9
  BorrowIncentivePoolsQueryInterface,
10
10
  BorrowIncentivePools,
@@ -14,7 +14,7 @@ import type {
14
14
  OptionalKeys,
15
15
  CoinPrices,
16
16
  MarketPools,
17
- } from '../types';
17
+ } from 'src/types';
18
18
  import BigNumber from 'bignumber.js';
19
19
  import { SuiObjectRef } from '@mysten/sui/client';
20
20