@scallop-io/sui-scallop-sdk 0.46.53 → 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.53",
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);
@@ -276,7 +276,7 @@ const generateSpoolQuickMethod: GenerateSpoolQuickMethod = ({
276
276
  }
277
277
 
278
278
  amount -= amountToUnstake;
279
- if (amount === 0) break;
279
+ if (amount <= 0) break;
280
280
  }
281
281
 
282
282
  if (toTransfer.length > 0) {
@@ -285,38 +285,6 @@ const generateSpoolQuickMethod: GenerateSpoolQuickMethod = ({
285
285
  if (toTransfer.length > 1) {
286
286
  txBlock.mergeCoins(mergedCoin, toTransfer.slice(1));
287
287
  }
288
-
289
- if (returnSCoin) {
290
- // check for existing sCoins
291
- try {
292
- const existingCoins = await builder.utils.selectCoins(
293
- Number.MAX_SAFE_INTEGER,
294
- builder.utils.parseSCoinType(stakeMarketCoinName),
295
- requireSender(txBlock)
296
- );
297
-
298
- if (existingCoins.length > 0) {
299
- txBlock.mergeCoins(mergedCoin, existingCoins);
300
- }
301
- } catch (e) {
302
- // ignore
303
- }
304
- } else {
305
- // check for existing market coins
306
- try {
307
- const existingCoins = await builder.utils.selectCoins(
308
- Number.MAX_SAFE_INTEGER,
309
- builder.utils.parseMarketCoinType(stakeMarketCoinName),
310
- requireSender(txBlock)
311
- );
312
-
313
- if (existingCoins.length > 0) {
314
- txBlock.mergeCoins(mergedCoin, existingCoins);
315
- }
316
- } catch (e) {
317
- // ignore
318
- }
319
- }
320
288
  return mergedCoin;
321
289
  }
322
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}`);
@@ -1010,17 +1029,13 @@ export class ScallopClient {
1010
1029
  toDestroyMarketCoin
1011
1030
  );
1012
1031
 
1013
- // check if current sCoin exist
1014
- try {
1015
- const existSCoins = await this.utils.selectCoins(
1016
- Number.MAX_SAFE_INTEGER,
1017
- this.utils.parseSCoinType(sCoinName as SupportSCoin),
1018
- this.walletAddress
1019
- );
1020
- txBlock.mergeCoins(sCoin, existSCoins);
1021
- } catch (e: any) {
1022
- // ignore
1023
- }
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
+ );
1024
1039
  sCoins.push(sCoin);
1025
1040
  }
1026
1041
  // check for staked market coin in spool
@@ -1,5 +1,5 @@
1
1
  import { SUI_TYPE_ARG, normalizeStructTag } from '@mysten/sui.js/utils';
2
- import { SuiAddressArg, SuiKit } from '@scallop-io/sui-kit';
2
+ import { SuiKit } from '@scallop-io/sui-kit';
3
3
  import { SuiPriceServiceConnection } from '@pythnetwork/pyth-sui-js';
4
4
  import { ScallopAddress } from './scallopAddress';
5
5
  import { ScallopQuery } from './scallopQuery';
@@ -26,6 +26,9 @@ import {
26
26
  parseAssetSymbol,
27
27
  findClosestUnlockRound,
28
28
  } from '../utils';
29
+ import { PYTH_ENDPOINTS } from 'src/constants/pyth';
30
+ import { ScallopCache } from './scallopCache';
31
+ import { DEFAULT_CACHE_OPTIONS } from 'src/constants/cache';
29
32
  import type {
30
33
  ScallopUtilsParams,
31
34
  ScallopInstanceParams,
@@ -39,9 +42,7 @@ import type {
39
42
  CoinWrappedType,
40
43
  SupportSCoin,
41
44
  } from '../types';
42
- import { PYTH_ENDPOINTS } from 'src/constants/pyth';
43
- import { ScallopCache } from './scallopCache';
44
- import { DEFAULT_CACHE_OPTIONS } from 'src/constants/cache';
45
+ import type { SuiAddressArg, SuiTxArg, SuiTxBlock } from '@scallop-io/sui-kit';
45
46
 
46
47
  /**
47
48
  * @description
@@ -396,6 +397,35 @@ export class ScallopUtils {
396
397
  return coins;
397
398
  }
398
399
 
400
+ /**
401
+ * Merge coins with type `coinType` to dest
402
+ * @param txBlock
403
+ * @param dest
404
+ * @param coinType
405
+ * @param sender
406
+ */
407
+ public async mergeSimilarCoins(
408
+ txBlock: SuiTxBlock,
409
+ dest: SuiTxArg,
410
+ coinType: string,
411
+ sender: string
412
+ ): Promise<void> {
413
+ // merge to existing coins if exist
414
+ try {
415
+ const existingSCoin = await this.selectCoins(
416
+ Number.MAX_SAFE_INTEGER,
417
+ coinType,
418
+ sender
419
+ );
420
+
421
+ if (existingSCoin.length > 0) {
422
+ txBlock.mergeCoins(dest, existingSCoin);
423
+ }
424
+ } catch (e) {
425
+ // ignore
426
+ }
427
+ }
428
+
399
429
  /**
400
430
  * Get all asset coin names in the obligation record by obligation id.
401
431
  *
@@ -408,12 +438,14 @@ export class ScallopUtils {
408
438
  */
409
439
  public async getObligationCoinNames(obligationId: SuiAddressArg) {
410
440
  const obligation = await queryObligation(this._query, obligationId);
411
- const collateralCoinTypes = obligation.collaterals.map((collateral) => {
412
- return `0x${collateral.type.name}`;
413
- });
414
- const debtCoinTypes = obligation.debts.map((debt) => {
415
- return `0x${debt.type.name}`;
416
- });
441
+ const collateralCoinTypes =
442
+ obligation?.collaterals.map((collateral) => {
443
+ return `0x${collateral.type.name}`;
444
+ }) ?? [];
445
+ const debtCoinTypes =
446
+ obligation?.debts.map((debt) => {
447
+ return `0x${debt.type.name}`;
448
+ }) ?? [];
417
449
  const obligationCoinTypes = [
418
450
  ...new Set([...collateralCoinTypes, ...debtCoinTypes]),
419
451
  ];