@scallop-io/sui-scallop-sdk 2.0.13-merge-split-ve-sca-alpha.5 → 2.1.0

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 (55) hide show
  1. package/dist/index.d.mts +622 -697
  2. package/dist/index.d.ts +622 -697
  3. package/dist/index.js +32 -33
  4. package/dist/index.mjs +10 -10
  5. package/package.json +8 -18
  6. package/src/builders/borrowIncentiveBuilder.ts +9 -21
  7. package/src/builders/coreBuilder.ts +2 -2
  8. package/src/builders/index.ts +2 -2
  9. package/src/builders/oracles/index.ts +2 -3
  10. package/src/builders/oracles/pyth.ts +2 -2
  11. package/src/builders/spoolBuilder.ts +2 -2
  12. package/src/builders/vescaBuilder.ts +14 -132
  13. package/src/constants/queryKeys.ts +29 -54
  14. package/src/constants/testAddress.ts +6 -12
  15. package/src/index.ts +11 -1
  16. package/src/models/index.ts +11 -9
  17. package/src/models/interface.ts +36 -0
  18. package/src/models/scallop.ts +38 -133
  19. package/src/models/scallopAddress.ts +127 -142
  20. package/src/models/scallopAxios.ts +185 -0
  21. package/src/models/scallopBuilder.ts +45 -75
  22. package/src/models/scallopClient.ts +124 -154
  23. package/src/models/scallopConstants.ts +248 -323
  24. package/src/models/scallopIndexer.ts +54 -98
  25. package/src/models/scallopQuery.ts +145 -190
  26. package/src/models/scallopQueryClient.ts +29 -0
  27. package/src/models/scallopSuiKit.ts +432 -0
  28. package/src/models/scallopUtils.ts +260 -164
  29. package/src/queries/borrowIncentiveQuery.ts +28 -16
  30. package/src/queries/borrowLimitQuery.ts +1 -1
  31. package/src/queries/coreQuery.ts +148 -107
  32. package/src/queries/flashloanFeeQuery.ts +12 -6
  33. package/src/queries/index.ts +0 -1
  34. package/src/queries/isolatedAssetQuery.ts +3 -3
  35. package/src/queries/loyaltyProgramQuery.ts +10 -8
  36. package/src/queries/ownerQuery.ts +32 -0
  37. package/src/queries/portfolioQuery.ts +4 -5
  38. package/src/queries/priceQuery.ts +14 -8
  39. package/src/queries/referralQuery.ts +9 -3
  40. package/src/queries/sCoinQuery.ts +4 -4
  41. package/src/queries/spoolQuery.ts +11 -11
  42. package/src/queries/supplyLimitQuery.ts +1 -1
  43. package/src/queries/switchboardQuery.ts +1 -1
  44. package/src/queries/vescaQuery.ts +31 -27
  45. package/src/queries/xOracleQuery.ts +13 -8
  46. package/src/types/address.ts +0 -3
  47. package/src/types/builder/core.ts +1 -1
  48. package/src/types/builder/vesca.ts +10 -20
  49. package/src/types/constant/queryKeys.ts +48 -0
  50. package/src/types/index.ts +0 -1
  51. package/src/utils/builder.ts +1 -1
  52. package/src/utils/util.ts +1 -33
  53. package/src/models/scallopCache.ts +0 -428
  54. package/src/queries/objectsQuery.ts +0 -18
  55. package/src/types/model.ts +0 -117
@@ -0,0 +1,29 @@
1
+ import { QueryClient, QueryClientConfig } from '@tanstack/query-core';
2
+ import { DEFAULT_CACHE_OPTIONS } from 'src/constants';
3
+
4
+ export type ScallopQueryClientParams = {
5
+ queryClient?: QueryClient;
6
+ queryClientConfig?: QueryClientConfig;
7
+ };
8
+
9
+ class ScallopQueryClient {
10
+ private _queryClient: QueryClient;
11
+ constructor(params: ScallopQueryClientParams = {}) {
12
+ this._queryClient =
13
+ params.queryClient ?? new QueryClient(this.defaultQueryClientConfig);
14
+ }
15
+
16
+ get queryClient() {
17
+ return this._queryClient;
18
+ }
19
+
20
+ set queryClient(queryClient: QueryClient) {
21
+ this._queryClient = queryClient;
22
+ }
23
+
24
+ get defaultQueryClientConfig() {
25
+ return DEFAULT_CACHE_OPTIONS;
26
+ }
27
+ }
28
+
29
+ export default ScallopQueryClient;
@@ -0,0 +1,432 @@
1
+ import {
2
+ DerivePathParams,
3
+ normalizeStructTag,
4
+ parseStructTag,
5
+ SuiKit,
6
+ SuiKitParams,
7
+ SuiObjectArg,
8
+ SuiTxBlock,
9
+ Transaction,
10
+ } from '@scallop-io/sui-kit';
11
+ import { queryKeys } from 'src/constants';
12
+ import {
13
+ CoinBalance,
14
+ DevInspectResults,
15
+ DynamicFieldPage,
16
+ GetBalanceParams,
17
+ GetDynamicFieldObjectParams,
18
+ GetDynamicFieldsParams,
19
+ GetOwnedObjectsParams,
20
+ SuiObjectData,
21
+ SuiObjectDataOptions,
22
+ SuiObjectResponse,
23
+ } from '@mysten/sui/client';
24
+ import { newSuiKit } from 'src/models/suiKit';
25
+ import { QueryKey } from '@tanstack/query-core';
26
+ import ScallopQueryClient, {
27
+ ScallopQueryClientParams,
28
+ } from './scallopQueryClient';
29
+
30
+ type QueryInspectTxnParams = {
31
+ queryTarget: string;
32
+ args: SuiObjectArg[];
33
+ typeArgs?: any[];
34
+ };
35
+
36
+ export type ScallopSuiKitParams = {
37
+ suiKit?: SuiKit;
38
+ tokensPerSecond?: number;
39
+ walletAddress?: string;
40
+ } & SuiKitParams &
41
+ ScallopQueryClientParams;
42
+
43
+ const DEFAULT_TOKENS_PER_INTERVAL = 10;
44
+
45
+ const deepMergeObject = <T>(curr: T, update: T): T => {
46
+ const result = { ...curr }; // Clone the current object to avoid mutation
47
+
48
+ for (const key in update) {
49
+ if (
50
+ update[key] &&
51
+ typeof update[key] === 'object' &&
52
+ !Array.isArray(update[key])
53
+ ) {
54
+ // If the value is an object, recurse
55
+ result[key] = deepMergeObject(
56
+ curr[key] || ({} as T[Extract<keyof T, string>]),
57
+ update[key]
58
+ );
59
+ } else {
60
+ // Otherwise, directly assign the value
61
+ result[key] = update[key];
62
+ }
63
+ }
64
+
65
+ return result;
66
+ };
67
+
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
+ class ScallopSuiKit extends ScallopQueryClient {
125
+ public readonly suiKit: SuiKit;
126
+ private _walletAddress: string;
127
+ private _tokensPerSecond: number;
128
+ private rateLimiter: RateLimiter;
129
+
130
+ constructor(params: ScallopSuiKitParams = {}) {
131
+ super(params);
132
+
133
+ this.suiKit = params.suiKit ?? newSuiKit(params);
134
+ this._tokensPerSecond =
135
+ params.tokensPerSecond ?? DEFAULT_TOKENS_PER_INTERVAL;
136
+ this.rateLimiter = new RateLimiter(this._tokensPerSecond);
137
+ this._walletAddress = params.walletAddress ?? this.suiKit.currentAddress;
138
+ }
139
+
140
+ switchFullNodes(fullNodes: string[]) {
141
+ this.suiKit.suiInteractor.switchFullNodes(fullNodes);
142
+ }
143
+
144
+ get client() {
145
+ return this.suiKit.client;
146
+ }
147
+
148
+ get walletAddress() {
149
+ return this._walletAddress;
150
+ }
151
+
152
+ set walletAddress(value: string) {
153
+ this._walletAddress = value;
154
+ }
155
+
156
+ get tokensPerSecond() {
157
+ return this._tokensPerSecond;
158
+ }
159
+
160
+ set tokensPerSecond(value: number) {
161
+ this._tokensPerSecond = value;
162
+ this.rateLimiter = new RateLimiter(this._tokensPerSecond);
163
+ }
164
+
165
+ get currentFullNode() {
166
+ return this.suiKit.suiInteractor.currentFullNode;
167
+ }
168
+
169
+ signAndSendTxn(
170
+ tx: Uint8Array | Transaction | SuiTxBlock,
171
+ derivePathParams?: DerivePathParams
172
+ ) {
173
+ return this.suiKit.signAndSendTxn(tx, derivePathParams);
174
+ }
175
+
176
+ private async callWithRateLimiter<T>(
177
+ queryKey: QueryKey,
178
+ fn: () => Promise<T> // Changed to function that returns Promise
179
+ ): Promise<T> {
180
+ return await this.queryClient.fetchQuery({
181
+ queryKey,
182
+ queryFn: () => this.rateLimiter.execute(fn), // Removed unnecessary async/await
183
+ });
184
+ }
185
+
186
+ private async queryGetNormalizedMoveFunction(target: string) {
187
+ const { address, module, name } = parseStructTag(target);
188
+ return await this.callWithRateLimiter(
189
+ queryKeys.rpc.getNormalizedMoveFunction({ target }),
190
+ () =>
191
+ this.client.getNormalizedMoveFunction({
192
+ // Wrapped in function
193
+ package: address,
194
+ module,
195
+ function: name,
196
+ })
197
+ );
198
+ }
199
+
200
+ /**
201
+ * @description Provides cache for getObject of the SuiKit.
202
+ * @param objectId
203
+ * @param QueryObjectParams
204
+ * @returns Promise<SuiObjectResponse>
205
+ */
206
+ async queryGetObject(objectId: string, options?: SuiObjectDataOptions) {
207
+ options = {
208
+ ...options,
209
+ showOwner: true,
210
+ showContent: true,
211
+ showType: true,
212
+ };
213
+ return await this.callWithRateLimiter(
214
+ queryKeys.rpc.getObject({
215
+ objectId,
216
+ options,
217
+ node: this.currentFullNode,
218
+ }),
219
+ () =>
220
+ this.client.getObject({
221
+ id: objectId,
222
+ options,
223
+ })
224
+ );
225
+ }
226
+
227
+ /**
228
+ * @description Provides cache for getObjects of the SuiKit.
229
+ * @param objectIds
230
+ * @returns Promise<SuiObjectData[]>
231
+ */
232
+ async queryGetObjects(
233
+ objectIds: string[],
234
+ options?: SuiObjectDataOptions
235
+ ): Promise<SuiObjectData[]> {
236
+ if (objectIds.length === 0) return [];
237
+ options ??= {
238
+ showContent: true,
239
+ showOwner: true,
240
+ showType: true,
241
+ };
242
+
243
+ const results = await this.callWithRateLimiter(
244
+ queryKeys.rpc.getObjects({
245
+ objectIds,
246
+ node: this.currentFullNode,
247
+ }),
248
+ () =>
249
+ this.suiKit.getObjects(objectIds, {
250
+ showOwner: options?.showOwner,
251
+ showContent: options?.showContent,
252
+ showType: options?.showType,
253
+ })
254
+ );
255
+
256
+ results.forEach((result) => {
257
+ // fetch previous data
258
+ const queryKey = queryKeys.rpc.getObject({
259
+ objectId: result.objectId,
260
+ node: this.currentFullNode,
261
+ });
262
+ const prevDatas = this.queryClient.getQueriesData<SuiObjectResponse>({
263
+ exact: false,
264
+ queryKey,
265
+ });
266
+ prevDatas.forEach(([key, prevData]) => {
267
+ this.queryClient.setQueryData(
268
+ key,
269
+ deepMergeObject(prevData, { data: result, error: null }),
270
+ { updatedAt: Date.now() }
271
+ );
272
+ });
273
+ });
274
+ return results;
275
+ }
276
+
277
+ /**
278
+ * @description Provides cache for getOwnedObjects of the SuiKit.
279
+ * @param input
280
+ * @returns Promise<PaginatedObjectsResponse>
281
+ */
282
+ async queryGetOwnedObjects(input: GetOwnedObjectsParams) {
283
+ // @TODO: This query need its own separate rate limiter (as owned objects can theoretically be infinite), need a better way to handle this
284
+ const results = await this.callWithRateLimiter(
285
+ queryKeys.rpc.getOwnedObjects(input),
286
+ () => this.client.getOwnedObjects(input)
287
+ );
288
+
289
+ if (results && results.data.length > 0) {
290
+ results.data
291
+ .filter(
292
+ (
293
+ result
294
+ ): result is typeof result & NonNullable<{ data: SuiObjectData }> =>
295
+ !!result.data
296
+ )
297
+ .forEach((result) => {
298
+ // fetch previous data
299
+ const queryKey = queryKeys.rpc.getObject({
300
+ objectId: result.data.objectId,
301
+ node: this.currentFullNode,
302
+ });
303
+ const prevDatas = this.queryClient.getQueriesData<SuiObjectResponse>({
304
+ exact: false,
305
+ queryKey,
306
+ });
307
+ prevDatas.forEach(([key, prevData]) => {
308
+ this.queryClient.setQueryData(
309
+ key,
310
+ deepMergeObject(prevData, { data: result.data, error: null }),
311
+ { updatedAt: Date.now() }
312
+ );
313
+ });
314
+ });
315
+ }
316
+ return results;
317
+ }
318
+
319
+ async queryGetDynamicFields(
320
+ input: GetDynamicFieldsParams
321
+ ): Promise<DynamicFieldPage | null> {
322
+ return await this.callWithRateLimiter(
323
+ queryKeys.rpc.getDynamicFields(input),
324
+ () => this.client.getDynamicFields(input)
325
+ );
326
+ }
327
+
328
+ async queryGetDynamicFieldObject(
329
+ input: GetDynamicFieldObjectParams
330
+ ): Promise<SuiObjectResponse | null> {
331
+ const result = await this.callWithRateLimiter(
332
+ queryKeys.rpc.getDynamicFieldObject(input),
333
+ () => this.client.getDynamicFieldObject(input)
334
+ );
335
+
336
+ if (result?.data) {
337
+ const queryKey = queryKeys.rpc.getObject({
338
+ objectId: result.data.objectId,
339
+ node: this.currentFullNode,
340
+ });
341
+ const prevDatas = this.queryClient.getQueriesData<SuiObjectResponse>({
342
+ exact: false,
343
+ queryKey,
344
+ });
345
+ prevDatas.forEach(([key, prevData]) => {
346
+ this.queryClient.setQueryData(
347
+ key,
348
+ deepMergeObject(prevData, { data: result.data, error: null }),
349
+ { updatedAt: Date.now() }
350
+ );
351
+ });
352
+ }
353
+ return result;
354
+ }
355
+
356
+ async queryGetAllCoinBalances(
357
+ owner: string
358
+ ): Promise<{ [k: string]: CoinBalance }> {
359
+ return await this.callWithRateLimiter(
360
+ queryKeys.rpc.getAllCoinBalances({
361
+ activeAddress: owner,
362
+ node: this.currentFullNode,
363
+ }),
364
+ async () => {
365
+ const allBalances = await this.client.getAllBalances({ owner });
366
+ if (!allBalances) return {};
367
+
368
+ const balances = allBalances.reduce(
369
+ (acc, coinBalance) => {
370
+ if (coinBalance.totalBalance !== '0') {
371
+ acc[normalizeStructTag(coinBalance.coinType)] = coinBalance;
372
+ }
373
+ return acc;
374
+ },
375
+ {} as { [k: string]: CoinBalance }
376
+ );
377
+
378
+ return balances;
379
+ }
380
+ );
381
+ }
382
+
383
+ async queryGetCoinBalance(
384
+ input: GetBalanceParams
385
+ ): Promise<CoinBalance | null> {
386
+ if (!input.coinType) return null;
387
+ const coinBalances = await this.queryGetAllCoinBalances(input.owner);
388
+ return coinBalances[normalizeStructTag(input.coinType)] ?? null;
389
+ }
390
+
391
+ /**
392
+ * @description Provides cache for inspectTxn of the SuiKit.
393
+ * @param QueryInspectTxnParams
394
+ * @param txBlock
395
+ * @returns Promise<DevInspectResults>
396
+ */
397
+ async queryInspectTxn({
398
+ queryTarget,
399
+ args,
400
+ typeArgs,
401
+ }: QueryInspectTxnParams): Promise<DevInspectResults | null> {
402
+ const txBlock = new SuiTxBlock();
403
+
404
+ const resolvedQueryTarget =
405
+ await this.queryGetNormalizedMoveFunction(queryTarget);
406
+ if (!resolvedQueryTarget) throw new Error('Invalid query target');
407
+
408
+ const resolvedArgs = await Promise.all(
409
+ (args ?? []).map(async (arg) => {
410
+ if (typeof arg !== 'string') return arg;
411
+
412
+ const cachedData = (await this.queryGetObject(arg))?.data;
413
+ if (!cachedData) return arg;
414
+
415
+ return cachedData;
416
+ })
417
+ );
418
+ txBlock.moveCall(queryTarget, resolvedArgs, typeArgs);
419
+
420
+ return await this.callWithRateLimiter(
421
+ queryKeys.rpc.getInspectTxn({
422
+ queryTarget,
423
+ args,
424
+ typeArgs,
425
+ node: this.currentFullNode,
426
+ }),
427
+ () => this.suiKit.inspectTxn(txBlock)
428
+ );
429
+ }
430
+ }
431
+
432
+ export default ScallopSuiKit;