@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.
- package/dist/index.d.mts +25 -28
- package/dist/index.d.ts +25 -28
- package/dist/index.js +24 -23
- package/dist/index.mjs +6 -6
- package/package.json +5 -2
- package/src/builders/borrowIncentiveBuilder.ts +4 -5
- package/src/builders/coreBuilder.ts +2 -2
- package/src/builders/index.ts +2 -2
- package/src/builders/spoolBuilder.ts +2 -2
- package/src/builders/vescaBuilder.ts +113 -14
- package/src/constants/testAddress.ts +12 -6
- package/src/models/scallop.ts +1 -1
- package/src/models/scallopAddress.ts +5 -2
- package/src/models/scallopBuilder.ts +1 -1
- package/src/models/scallopCache.ts +80 -118
- package/src/models/scallopClient.ts +1 -1
- package/src/models/scallopConstants.ts +16 -8
- package/src/models/scallopIndexer.ts +1 -1
- package/src/queries/borrowIncentiveQuery.ts +3 -3
- package/src/queries/coreQuery.ts +121 -154
- package/src/queries/portfolioQuery.ts +2 -2
- package/src/queries/spoolQuery.ts +76 -74
- package/src/queries/vescaQuery.ts +2 -2
- package/src/types/address.ts +3 -0
- package/src/types/builder/vesca.ts +20 -10
- package/src/types/model.ts +2 -1
- package/src/utils/builder.ts +1 -1
|
@@ -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
|
|
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.
|
|
105
|
-
|
|
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.
|
|
242
|
-
|
|
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.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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.
|
|
317
|
-
|
|
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.
|
|
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.
|
|
396
|
-
|
|
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.
|
|
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.
|
|
441
|
-
|
|
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(
|
|
@@ -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.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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)
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
parseOriginBorrowIncentivePoolData,
|
|
4
4
|
parseOriginBorrowIncentiveAccountData,
|
|
5
5
|
calculateBorrowIncentivePoolPointData,
|
|
6
|
-
} from '
|
|
7
|
-
import type { ScallopAddress, ScallopQuery, ScallopUtils } from '
|
|
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 '
|
|
17
|
+
} from 'src/types';
|
|
18
18
|
import BigNumber from 'bignumber.js';
|
|
19
19
|
import { SuiObjectRef } from '@mysten/sui/client';
|
|
20
20
|
|