@scallop-io/sui-scallop-sdk 0.46.52 → 0.46.54

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.
@@ -8,6 +8,7 @@ import type { ScallopParams } from '../types/';
8
8
  import { ScallopIndexer } from './scallopIndexer';
9
9
  import { ScallopCache } from './scallopCache';
10
10
  import { QueryClientConfig } from '@tanstack/query-core';
11
+ import { TokenBucket } from 'src/utils';
11
12
  /**
12
13
  * @argument params - The parameters for the Scallop instance.
13
14
  * @argument cacheOptions - The cache options for the QueryClient.
@@ -30,7 +31,7 @@ export declare class Scallop {
30
31
  suiKit: SuiKit;
31
32
  cache: ScallopCache;
32
33
  private _address;
33
- constructor(params: ScallopParams, cacheOptions?: QueryClientConfig);
34
+ constructor(params: ScallopParams, cacheOptions?: QueryClientConfig, tokenBucket?: TokenBucket);
34
35
  /**
35
36
  * Get a scallop address instance that already has read addresses.
36
37
  *
@@ -1,7 +1,8 @@
1
1
  import { QueryClient, QueryClientConfig } from '@tanstack/query-core';
2
2
  import { SuiTxArg } from '@scallop-io/sui-kit';
3
3
  import { SuiKit } from '@scallop-io/sui-kit';
4
- import type { SuiObjectResponse, SuiObjectDataOptions, SuiObjectData, PaginatedObjectsResponse, GetOwnedObjectsParams, DevInspectResults, GetDynamicFieldsParams, DynamicFieldPage, GetDynamicFieldObjectParams, GetBalanceParams } from '@mysten/sui.js/client';
4
+ import type { SuiObjectResponse, SuiObjectDataOptions, SuiObjectData, GetOwnedObjectsParams, DevInspectResults, GetDynamicFieldsParams, DynamicFieldPage, GetDynamicFieldObjectParams, GetBalanceParams } from '@mysten/sui.js/client';
5
+ import { TokenBucket } from 'src/utils';
5
6
  type QueryInspectTxnParams = {
6
7
  queryTarget: string;
7
8
  args: SuiTxArg[];
@@ -22,8 +23,10 @@ type QueryInspectTxnParams = {
22
23
  export declare class ScallopCache {
23
24
  readonly queryClient: QueryClient;
24
25
  readonly _suiKit?: SuiKit;
25
- constructor(cacheOptions?: QueryClientConfig, suiKit?: SuiKit);
26
+ private tokenBucket;
27
+ constructor(cacheOptions?: QueryClientConfig, suiKit?: SuiKit, tokenBucket?: TokenBucket);
26
28
  private get suiKit();
29
+ private get client();
27
30
  /**
28
31
  * @description Invalidate cache based on the refetchType parameter
29
32
  * @param refetchType Determines the type of queries to be refetched. Defaults to `active`.
@@ -38,21 +41,21 @@ export declare class ScallopCache {
38
41
  * @description Cache protocol config call for 60 seconds.
39
42
  * @returns Promise<ProtocolConfig>
40
43
  */
41
- getProtocolConfig(): Promise<import("@mysten/sui.js/client").ProtocolConfig>;
44
+ getProtocolConfig(): Promise<import("@mysten/sui.js/client").ProtocolConfig | null>;
42
45
  /**
43
46
  * @description Provides cache for inspectTxn of the SuiKit.
44
47
  * @param QueryInspectTxnParams
45
48
  * @param txBlock
46
49
  * @returns Promise<DevInspectResults>
47
50
  */
48
- queryInspectTxn({ queryTarget, args, typeArgs, }: QueryInspectTxnParams): Promise<DevInspectResults>;
51
+ queryInspectTxn({ queryTarget, args, typeArgs, }: QueryInspectTxnParams): Promise<DevInspectResults | null>;
49
52
  /**
50
53
  * @description Provides cache for getObject of the SuiKit.
51
54
  * @param objectId
52
55
  * @param QueryObjectParams
53
56
  * @returns Promise<SuiObjectResponse>
54
57
  */
55
- queryGetObject(objectId: string, options?: SuiObjectDataOptions): Promise<SuiObjectResponse>;
58
+ queryGetObject(objectId: string, options?: SuiObjectDataOptions): Promise<SuiObjectResponse | null>;
56
59
  /**
57
60
  * @description Provides cache for getObjects of the SuiKit.
58
61
  * @param objectIds
@@ -64,9 +67,9 @@ export declare class ScallopCache {
64
67
  * @param input
65
68
  * @returns Promise<PaginatedObjectsResponse>
66
69
  */
67
- queryGetOwnedObjects(input: GetOwnedObjectsParams): Promise<PaginatedObjectsResponse>;
68
- queryGetDynamicFields(input: GetDynamicFieldsParams): Promise<DynamicFieldPage>;
69
- queryGetDynamicFieldObject(input: GetDynamicFieldObjectParams): Promise<SuiObjectResponse>;
70
+ queryGetOwnedObjects(input: GetOwnedObjectsParams): Promise<import("@mysten/sui.js/client").PaginatedObjectsResponse | null>;
71
+ queryGetDynamicFields(input: GetDynamicFieldsParams): Promise<DynamicFieldPage | null>;
72
+ queryGetDynamicFieldObject(input: GetDynamicFieldObjectParams): Promise<SuiObjectResponse | null>;
70
73
  queryGetAllCoinBalances(owner: string): Promise<{
71
74
  [k: string]: string;
72
75
  }>;
@@ -3,11 +3,11 @@ import { ScallopAddress } from './scallopAddress';
3
3
  import { ScallopUtils } from './scallopUtils';
4
4
  import { ScallopBuilder } from './scallopBuilder';
5
5
  import { ScallopQuery } from './scallopQuery';
6
+ import { ScallopCache } from './scallopCache';
6
7
  import type { SuiTransactionBlockResponse } from '@mysten/sui.js/client';
7
8
  import type { TransactionObjectArgument } from '@mysten/sui.js/transactions';
8
9
  import type { SuiObjectArg } from '@scallop-io/sui-kit';
9
10
  import type { ScallopClientFnReturnType, ScallopInstanceParams, ScallopClientParams, SupportPoolCoins, SupportCollateralCoins, SupportAssetCoins, SupportStakeCoins, SupportStakeMarketCoins, SupportBorrowIncentiveCoins, ScallopTxBlock } from '../types';
10
- import { ScallopCache } from './scallopCache';
11
11
  /**
12
12
  * @description
13
13
  * It provides contract interaction operations for general users.
@@ -64,7 +64,7 @@ export declare class ScallopClient {
64
64
  * @param obligationId - The obligation id.
65
65
  * @return Obligation data.
66
66
  */
67
- queryObligation(obligationId: string): Promise<import("../types").ObligationQueryInterface>;
67
+ queryObligation(obligationId: string): Promise<import("../types").ObligationQueryInterface | undefined>;
68
68
  /**
69
69
  * Query all stake accounts data.
70
70
  *
@@ -118,7 +118,7 @@ export declare class ScallopQuery {
118
118
  * @param obligationId - The obligation id.
119
119
  * @return Obligation data.
120
120
  */
121
- queryObligation(obligationId: string): Promise<import("../types").ObligationQueryInterface>;
121
+ queryObligation(obligationId: string): Promise<import("../types").ObligationQueryInterface | undefined>;
122
122
  /**
123
123
  * Get all asset coin amounts.
124
124
  *
@@ -1,6 +1,6 @@
1
- import { SuiAddressArg } from '@scallop-io/sui-kit';
2
1
  import { ScallopAddress } from './scallopAddress';
3
2
  import type { ScallopUtilsParams, ScallopInstanceParams, SupportCoins, SupportAssetCoins, SupportMarketCoins, SupportStakeMarketCoins, SupportBorrowIncentiveCoins, CoinWrappedType, SupportSCoin } from '../types';
3
+ import type { SuiAddressArg, SuiTxArg, SuiTxBlock } from '@scallop-io/sui-kit';
4
4
  /**
5
5
  * @description
6
6
  * Integrates some helper functions frequently used in interactions with the Scallop contract.
@@ -148,6 +148,14 @@ export declare class ScallopUtils {
148
148
  version: string;
149
149
  balance: string;
150
150
  }[]>;
151
+ /**
152
+ * Merge coins with type `coinType` to dest
153
+ * @param txBlock
154
+ * @param dest
155
+ * @param coinType
156
+ * @param sender
157
+ */
158
+ mergeSimilarCoins(txBlock: SuiTxBlock, dest: SuiTxArg, coinType: string, sender: string): Promise<void>;
151
159
  /**
152
160
  * Get all asset coin names in the obligation record by obligation id.
153
161
  *
@@ -113,7 +113,7 @@ export declare const getObligationLocked: (query: ScallopQuery, obligationId: st
113
113
  * @param obligationId - The obligation id.
114
114
  * @return Obligation data.
115
115
  */
116
- export declare const queryObligation: (query: ScallopQuery, obligationId: SuiAddressArg) => Promise<ObligationQueryInterface>;
116
+ export declare const queryObligation: (query: ScallopQuery, obligationId: SuiAddressArg) => Promise<ObligationQueryInterface | undefined>;
117
117
  /**
118
118
  * Query all owned coin amount.
119
119
  *
@@ -1,3 +1,4 @@
1
1
  export * from './builder';
2
2
  export * from './query';
3
3
  export * from './util';
4
+ export * from './tokenBucket';
@@ -0,0 +1,11 @@
1
+ declare class TokenBucket {
2
+ private tokensPerInterval;
3
+ private interval;
4
+ private tokens;
5
+ private lastRefill;
6
+ constructor(tokensPerInterval: number, intervalInMs: number);
7
+ refill(): void;
8
+ removeTokens(count: number): boolean;
9
+ }
10
+ declare const callWithRateLimit: <T>(tokenBucket: TokenBucket, fn: () => Promise<T>, retryDelayInMs?: number, maxRetries?: number) => Promise<T | null>;
11
+ export { TokenBucket, callWithRateLimit };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scallop-io/sui-scallop-sdk",
3
- "version": "0.46.52",
3
+ "version": "0.46.54",
4
4
  "description": "Typescript sdk for interacting with Scallop contract on SUI",
5
5
  "keywords": [
6
6
  "sui",
@@ -47,18 +47,12 @@ const generateLoyaltyProgramQuickMethod: GenerateLoyaltyProgramQuickMethod = ({
47
47
  const rewardCoin = txBlock.claimLoyaltyRevenue(veScaKey);
48
48
 
49
49
  // get existing sca coin to merge with
50
- try {
51
- const existingScaCoin = await builder.suiKit.suiInteractor.selectCoins(
52
- sender,
53
- Infinity,
54
- coinIds.sca
55
- );
56
- txBlock.mergeCoins(rewardCoin, existingScaCoin);
57
- } catch (e) {
58
- // ignore
59
- } finally {
60
- toTransferObject.push(rewardCoin);
61
- }
50
+ await builder.utils.mergeSimilarCoins(
51
+ txBlock,
52
+ rewardCoin,
53
+ coinIds.sca,
54
+ requireSender(txBlock)
55
+ );
62
56
 
63
57
  if (toTransferObject.length > 0) {
64
58
  txBlock.transferObjects(toTransferObject, sender);
@@ -90,15 +90,15 @@ const stakeHelper = async (
90
90
  builder: ScallopBuilder,
91
91
  txBlock: SuiTxBlockWithSpoolNormalMethods,
92
92
  stakeAccount: SuiAddressArg,
93
- coinType: string,
94
93
  coinName: SupportStakeMarketCoins,
95
94
  amount: number,
96
95
  sender: string,
97
96
  isSCoin: boolean = false
98
97
  ) => {
99
98
  try {
100
- const coins = await builder.utils.selectCoins(amount, coinType, sender);
101
- const [takeCoin, leftCoin] = txBlock.takeAmountFromCoins(coins, amount);
99
+ const { takeCoin, leftCoin, totalAmount } = isSCoin
100
+ ? await builder.selectSCoin(txBlock, coinName, amount, sender)
101
+ : await builder.selectMarketCoin(txBlock, coinName, amount, sender);
102
102
  if (isSCoin) {
103
103
  const marketCoin = txBlock.burnSCoin(coinName, takeCoin);
104
104
  txBlock.stake(stakeAccount, marketCoin, coinName);
@@ -106,9 +106,9 @@ const stakeHelper = async (
106
106
  txBlock.stake(stakeAccount, takeCoin, coinName);
107
107
  }
108
108
  txBlock.transferObjects([leftCoin], sender);
109
- return true;
109
+ return totalAmount;
110
110
  } catch (e) {
111
- return false;
111
+ return 0;
112
112
  }
113
113
  };
114
114
 
@@ -213,28 +213,24 @@ const generateSpoolQuickMethod: GenerateSpoolQuickMethod = ({
213
213
  stakeAccountId
214
214
  );
215
215
 
216
- const marketCoinType =
217
- builder.utils.parseMarketCoinType(stakeMarketCoinName);
218
- const sCoinType = builder.utils.parseSCoinType(stakeMarketCoinName);
219
216
  if (typeof amountOrMarketCoin === 'number') {
220
217
  // try stake market coin
221
- const stakeMarketCoinRes = await stakeHelper(
218
+ const stakedMarketCoinAmount = await stakeHelper(
222
219
  builder,
223
220
  txBlock,
224
221
  stakeAccountIds[0],
225
- marketCoinType,
226
222
  stakeMarketCoinName,
227
223
  amountOrMarketCoin,
228
224
  sender
229
225
  );
230
226
 
227
+ amountOrMarketCoin -= stakedMarketCoinAmount;
231
228
  // no market coin, try sCoin
232
- if (!stakeMarketCoinRes) {
229
+ if (amountOrMarketCoin > 0) {
233
230
  await stakeHelper(
234
231
  builder,
235
232
  txBlock,
236
233
  stakeAccountIds[0],
237
- sCoinType,
238
234
  stakeMarketCoinName,
239
235
  amountOrMarketCoin,
240
236
  sender,
@@ -280,7 +276,7 @@ const generateSpoolQuickMethod: GenerateSpoolQuickMethod = ({
280
276
  }
281
277
 
282
278
  amount -= amountToUnstake;
283
- if (amount === 0) break;
279
+ if (amount <= 0) break;
284
280
  }
285
281
 
286
282
  if (toTransfer.length > 0) {
@@ -289,38 +285,6 @@ const generateSpoolQuickMethod: GenerateSpoolQuickMethod = ({
289
285
  if (toTransfer.length > 1) {
290
286
  txBlock.mergeCoins(mergedCoin, toTransfer.slice(1));
291
287
  }
292
-
293
- if (returnSCoin) {
294
- // check for existing sCoins
295
- try {
296
- const existingCoins = await builder.utils.selectCoins(
297
- Number.MAX_SAFE_INTEGER,
298
- builder.utils.parseSCoinType(stakeMarketCoinName),
299
- requireSender(txBlock)
300
- );
301
-
302
- if (existingCoins.length > 0) {
303
- txBlock.mergeCoins(mergedCoin, existingCoins);
304
- }
305
- } catch (e) {
306
- // ignore
307
- }
308
- } else {
309
- // check for existing market coins
310
- try {
311
- const existingCoins = await builder.utils.selectCoins(
312
- Number.MAX_SAFE_INTEGER,
313
- builder.utils.parseMarketCoinType(stakeMarketCoinName),
314
- requireSender(txBlock)
315
- );
316
-
317
- if (existingCoins.length > 0) {
318
- txBlock.mergeCoins(mergedCoin, existingCoins);
319
- }
320
- } catch (e) {
321
- // ignore
322
- }
323
- }
324
288
  return mergedCoin;
325
289
  }
326
290
  },
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_TOKENS_PER_INTERVAL = 10;
2
+ export const DEFAULT_INTERVAL_IN_MS = 1000;
@@ -10,6 +10,11 @@ import { ScallopIndexer } from './scallopIndexer';
10
10
  import { ScallopCache } from './scallopCache';
11
11
  import { QueryClientConfig } from '@tanstack/query-core';
12
12
  import { DEFAULT_CACHE_OPTIONS } from 'src/constants/cache';
13
+ import { TokenBucket } from 'src/utils';
14
+ import {
15
+ DEFAULT_INTERVAL_IN_MS,
16
+ DEFAULT_TOKENS_PER_INTERVAL,
17
+ } from 'src/constants/tokenBucket';
13
18
 
14
19
  /**
15
20
  * @argument params - The parameters for the Scallop instance.
@@ -35,12 +40,18 @@ export class Scallop {
35
40
 
36
41
  private _address: ScallopAddress;
37
42
 
38
- public constructor(params: ScallopParams, cacheOptions?: QueryClientConfig) {
43
+ public constructor(
44
+ params: ScallopParams,
45
+ cacheOptions?: QueryClientConfig,
46
+ tokenBucket?: TokenBucket
47
+ ) {
39
48
  this.params = params;
40
49
  this.suiKit = new SuiKit(params);
41
50
  this.cache = new ScallopCache(
42
51
  cacheOptions ?? DEFAULT_CACHE_OPTIONS,
43
- this.suiKit
52
+ this.suiKit,
53
+ tokenBucket ??
54
+ new TokenBucket(DEFAULT_TOKENS_PER_INTERVAL, DEFAULT_INTERVAL_IN_MS)
44
55
  );
45
56
  this._address = new ScallopAddress(
46
57
  {
@@ -10,15 +10,20 @@ import type {
10
10
  SuiObjectResponse,
11
11
  SuiObjectDataOptions,
12
12
  SuiObjectData,
13
- PaginatedObjectsResponse,
14
13
  GetOwnedObjectsParams,
15
14
  DevInspectResults,
16
15
  GetDynamicFieldsParams,
17
16
  DynamicFieldPage,
18
17
  GetDynamicFieldObjectParams,
19
18
  GetBalanceParams,
19
+ SuiClient,
20
20
  } from '@mysten/sui.js/client';
21
21
  import { DEFAULT_CACHE_OPTIONS } from 'src/constants/cache';
22
+ import { callWithRateLimit, TokenBucket } from 'src/utils';
23
+ import {
24
+ DEFAULT_INTERVAL_IN_MS,
25
+ DEFAULT_TOKENS_PER_INTERVAL,
26
+ } from 'src/constants/tokenBucket';
22
27
 
23
28
  type QueryInspectTxnParams = {
24
29
  queryTarget: string;
@@ -42,10 +47,18 @@ type QueryInspectTxnParams = {
42
47
  export class ScallopCache {
43
48
  public readonly queryClient: QueryClient;
44
49
  public readonly _suiKit?: SuiKit;
50
+ private tokenBucket: TokenBucket;
45
51
 
46
- public constructor(cacheOptions?: QueryClientConfig, suiKit?: SuiKit) {
52
+ public constructor(
53
+ cacheOptions?: QueryClientConfig,
54
+ suiKit?: SuiKit,
55
+ tokenBucket?: TokenBucket
56
+ ) {
47
57
  this.queryClient = new QueryClient(cacheOptions ?? DEFAULT_CACHE_OPTIONS);
48
58
  this._suiKit = suiKit;
59
+ this.tokenBucket =
60
+ tokenBucket ??
61
+ new TokenBucket(DEFAULT_TOKENS_PER_INTERVAL, DEFAULT_INTERVAL_IN_MS);
49
62
  }
50
63
 
51
64
  private get suiKit(): SuiKit {
@@ -55,6 +68,10 @@ export class ScallopCache {
55
68
  return this._suiKit;
56
69
  }
57
70
 
71
+ private get client(): SuiClient {
72
+ return this.suiKit.client();
73
+ }
74
+
58
75
  /**
59
76
  * @description Invalidate cache based on the refetchType parameter
60
77
  * @param refetchType Determines the type of queries to be refetched. Defaults to `active`.
@@ -80,7 +97,9 @@ export class ScallopCache {
80
97
  return await this.queryClient.fetchQuery({
81
98
  queryKey: ['getProtocolConfig'],
82
99
  queryFn: async () => {
83
- return await this.suiKit.client().getProtocolConfig();
100
+ return await callWithRateLimit(this.tokenBucket, () =>
101
+ this.client.getProtocolConfig()
102
+ );
84
103
  },
85
104
  staleTime: 30000,
86
105
  });
@@ -96,14 +115,14 @@ export class ScallopCache {
96
115
  queryTarget,
97
116
  args,
98
117
  typeArgs,
99
- }: QueryInspectTxnParams): Promise<DevInspectResults> {
118
+ }: QueryInspectTxnParams): Promise<DevInspectResults | null> {
100
119
  const txBlock = new SuiTxBlock();
101
120
 
102
121
  // resolve all the object args to prevent duplicate getNormalizedMoveFunction calls
103
122
  const resolvedArgs = await Promise.all(
104
123
  args.map(async (arg) => {
105
124
  if (typeof arg === 'string') {
106
- return (await this.queryGetObject(arg, { showContent: true })).data;
125
+ return (await this.queryGetObject(arg, { showContent: true }))?.data;
107
126
  }
108
127
  return arg;
109
128
  })
@@ -112,9 +131,9 @@ export class ScallopCache {
112
131
 
113
132
  // build the txBlock to prevent duplicate getProtocolConfig calls
114
133
  const txBytes = await txBlock.txBlock.build({
115
- client: this.suiKit.client(),
134
+ client: this.client,
116
135
  onlyTransactionKind: true,
117
- protocolConfig: await this.getProtocolConfig(),
136
+ protocolConfig: (await this.getProtocolConfig()) ?? undefined,
118
137
  });
119
138
 
120
139
  const query = await this.queryClient.fetchQuery({
@@ -127,7 +146,9 @@ export class ScallopCache {
127
146
  JSON.stringify(typeArgs),
128
147
  ],
129
148
  queryFn: async () => {
130
- return await this.suiKit.inspectTxn(txBytes);
149
+ return await callWithRateLimit(this.tokenBucket, () =>
150
+ this.suiKit.inspectTxn(txBytes)
151
+ );
131
152
  },
132
153
  });
133
154
  return query;
@@ -142,7 +163,7 @@ export class ScallopCache {
142
163
  public async queryGetObject(
143
164
  objectId: string,
144
165
  options?: SuiObjectDataOptions
145
- ): Promise<SuiObjectResponse> {
166
+ ): Promise<SuiObjectResponse | null> {
146
167
  const queryKey = ['getObject', objectId, this.suiKit.currentAddress()];
147
168
  if (options) {
148
169
  queryKey.push(JSON.stringify(options));
@@ -150,10 +171,12 @@ export class ScallopCache {
150
171
  return this.queryClient.fetchQuery({
151
172
  queryKey,
152
173
  queryFn: async () => {
153
- return await this.suiKit.client().getObject({
154
- id: objectId,
155
- options,
156
- });
174
+ return await callWithRateLimit(this.tokenBucket, () =>
175
+ this.client.getObject({
176
+ id: objectId,
177
+ options,
178
+ })
179
+ );
157
180
  },
158
181
  });
159
182
  }
@@ -179,7 +202,9 @@ export class ScallopCache {
179
202
  return this.queryClient.fetchQuery({
180
203
  queryKey: queryKey,
181
204
  queryFn: async () => {
182
- return await this.suiKit.getObjects(objectIds, options);
205
+ return await callWithRateLimit(this.tokenBucket, () =>
206
+ this.suiKit.getObjects(objectIds, options)
207
+ );
183
208
  },
184
209
  });
185
210
  }
@@ -189,9 +214,7 @@ export class ScallopCache {
189
214
  * @param input
190
215
  * @returns Promise<PaginatedObjectsResponse>
191
216
  */
192
- public async queryGetOwnedObjects(
193
- input: GetOwnedObjectsParams
194
- ): Promise<PaginatedObjectsResponse> {
217
+ public async queryGetOwnedObjects(input: GetOwnedObjectsParams) {
195
218
  const queryKey = ['getOwnedObjects', input.owner];
196
219
  if (input.cursor) {
197
220
  queryKey.push(JSON.stringify(input.cursor));
@@ -209,14 +232,16 @@ export class ScallopCache {
209
232
  return this.queryClient.fetchQuery({
210
233
  queryKey,
211
234
  queryFn: async () => {
212
- return await this.suiKit.client().getOwnedObjects(input);
235
+ return await callWithRateLimit(this.tokenBucket, () =>
236
+ this.client.getOwnedObjects(input)
237
+ );
213
238
  },
214
239
  });
215
240
  }
216
241
 
217
242
  public async queryGetDynamicFields(
218
243
  input: GetDynamicFieldsParams
219
- ): Promise<DynamicFieldPage> {
244
+ ): Promise<DynamicFieldPage | null> {
220
245
  const queryKey = ['getDynamicFields', input.parentId];
221
246
  if (input.cursor) {
222
247
  queryKey.push(JSON.stringify(input.cursor));
@@ -228,14 +253,16 @@ export class ScallopCache {
228
253
  return this.queryClient.fetchQuery({
229
254
  queryKey,
230
255
  queryFn: async () => {
231
- return await this.suiKit.client().getDynamicFields(input);
256
+ return await callWithRateLimit(this.tokenBucket, () =>
257
+ this.client.getDynamicFields(input)
258
+ );
232
259
  },
233
260
  });
234
261
  }
235
262
 
236
263
  public async queryGetDynamicFieldObject(
237
264
  input: GetDynamicFieldObjectParams
238
- ): Promise<SuiObjectResponse> {
265
+ ): Promise<SuiObjectResponse | null> {
239
266
  const queryKey = [
240
267
  'getDynamicFieldObject',
241
268
  input.parentId,
@@ -245,7 +272,9 @@ export class ScallopCache {
245
272
  return this.queryClient.fetchQuery({
246
273
  queryKey,
247
274
  queryFn: async () => {
248
- return await this.suiKit.client().getDynamicFieldObject(input);
275
+ return await callWithRateLimit(this.tokenBucket, () =>
276
+ this.client.getDynamicFieldObject(input)
277
+ );
249
278
  },
250
279
  });
251
280
  }
@@ -257,9 +286,10 @@ export class ScallopCache {
257
286
  return this.queryClient.fetchQuery({
258
287
  queryKey,
259
288
  queryFn: async () => {
260
- const allBalances = await this.suiKit
261
- .client()
262
- .getAllBalances({ owner });
289
+ const allBalances = await callWithRateLimit(this.tokenBucket, () =>
290
+ this.client.getAllBalances({ owner })
291
+ );
292
+ if (!allBalances) return {};
263
293
  const balances = allBalances.reduce(
264
294
  (acc, coinBalance) => {
265
295
  if (coinBalance.totalBalance !== '0') {
@@ -1,5 +1,6 @@
1
1
  import { normalizeSuiAddress } from '@mysten/sui.js/utils';
2
2
  import { SuiKit } from '@scallop-io/sui-kit';
3
+ import { DEFAULT_CACHE_OPTIONS } from 'src/constants/cache';
3
4
  import {
4
5
  ADDRESSES_ID,
5
6
  SUPPORT_BORROW_INCENTIVE_POOLS,
@@ -11,6 +12,8 @@ import { ScallopAddress } from './scallopAddress';
11
12
  import { ScallopUtils } from './scallopUtils';
12
13
  import { ScallopBuilder } from './scallopBuilder';
13
14
  import { ScallopQuery } from './scallopQuery';
15
+ import { ScallopCache } from './scallopCache';
16
+ import { requireSender } from 'src/utils';
14
17
  import type { SuiTransactionBlockResponse } from '@mysten/sui.js/client';
15
18
  import type { TransactionObjectArgument } from '@mysten/sui.js/transactions';
16
19
  import type { SuiObjectArg } from '@scallop-io/sui-kit';
@@ -27,8 +30,6 @@ import type {
27
30
  ScallopTxBlock,
28
31
  SupportSCoin,
29
32
  } from '../types';
30
- import { ScallopCache } from './scallopCache';
31
- import { DEFAULT_CACHE_OPTIONS } from 'src/constants/cache';
32
33
 
33
34
  /**
34
35
  * @description
@@ -747,6 +748,17 @@ export class ScallopClient {
747
748
  stakeMarketCoinName,
748
749
  stakeAccountId
749
750
  );
751
+
752
+ if (sCoin) {
753
+ // merge to existing sCoins if exist
754
+ await this.utils.mergeSimilarCoins(
755
+ txBlock,
756
+ sCoin,
757
+ this.utils.parseSCoinType(stakeMarketCoinName),
758
+ requireSender(txBlock)
759
+ );
760
+ }
761
+
750
762
  txBlock.transferObjects([sCoin], sender);
751
763
 
752
764
  if (sign) {
@@ -800,6 +812,13 @@ export class ScallopClient {
800
812
  this.utils.parseCoinName<SupportStakeCoins>(stakeMarketCoinName);
801
813
  if (stakeMarketCoin) {
802
814
  const coin = txBlock.withdraw(stakeMarketCoin, stakeCoinName);
815
+ await this.utils.mergeSimilarCoins(
816
+ txBlock,
817
+ coin,
818
+ this.utils.parseCoinType(this.utils.parseCoinName(stakeCoinName)),
819
+ requireSender(txBlock)
820
+ );
821
+
803
822
  txBlock.transferObjects([coin], sender);
804
823
  } else {
805
824
  throw new Error(`No stake found for ${stakeMarketCoinName}`);
@@ -991,12 +1010,10 @@ export class ScallopClient {
991
1010
  this.walletAddress
992
1011
  ); // throw error no coins found
993
1012
 
994
- const mergedMarketCoin = marketCoins[0];
1013
+ toDestroyMarketCoin = marketCoins[0];
995
1014
  if (marketCoins.length > 1) {
996
- txBlock.mergeCoins(mergedMarketCoin, marketCoins.slice(1));
1015
+ txBlock.mergeCoins(toDestroyMarketCoin, marketCoins.slice(1));
997
1016
  }
998
-
999
- toDestroyMarketCoin = mergedMarketCoin;
1000
1017
  } catch (e: any) {
1001
1018
  // Ignore
1002
1019
  const errMsg = e.toString() as String;
@@ -1012,29 +1029,15 @@ export class ScallopClient {
1012
1029
  toDestroyMarketCoin
1013
1030
  );
1014
1031
 
1015
- // check if current sCoin exist
1016
- try {
1017
- const existSCoins = await this.utils.selectCoins(
1018
- Number.MAX_SAFE_INTEGER,
1019
- this.utils.parseSCoinType(sCoinName as SupportSCoin),
1020
- this.walletAddress
1021
- ); // throw error on no coins found
1022
- const mergedSCoin = existSCoins[0];
1023
- if (existSCoins.length > 1) {
1024
- txBlock.mergeCoins(mergedSCoin, existSCoins.slice(1));
1025
- }
1026
-
1027
- // merge existing sCoin to new sCoin
1028
- txBlock.mergeCoins(sCoin, [mergedSCoin]);
1029
- } catch (e: any) {
1030
- // ignore
1031
- const errMsg = e.toString() as String;
1032
- if (!errMsg.includes('No valid coins found for the transaction'))
1033
- throw e;
1034
- }
1032
+ // check if current sCoin
1033
+ await this.utils.mergeSimilarCoins(
1034
+ txBlock,
1035
+ sCoin,
1036
+ this.utils.parseSCoinType(sCoinName as SupportSCoin),
1037
+ requireSender(txBlock)
1038
+ );
1035
1039
  sCoins.push(sCoin);
1036
1040
  }
1037
-
1038
1041
  // check for staked market coin in spool
1039
1042
  if (SUPPORT_SPOOLS.includes(sCoinName as SupportStakeMarketCoins)) {
1040
1043
  try {
@@ -1047,8 +1050,6 @@ export class ScallopClient {
1047
1050
  }
1048
1051
  } catch (e: any) {
1049
1052
  // ignore
1050
- const errMsg = e.toString();
1051
- if (!errMsg.includes('No stake account found')) throw e;
1052
1053
  }
1053
1054
  }
1054
1055