@mmt-finance/amm-sui-sdk 0.0.1-beta

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.
@@ -0,0 +1,3 @@
1
+ export default {
2
+ extends: ["@commitlint/config-conventional"],
3
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@mmt-finance/amm-sui-sdk",
3
+ "version": "0.0.1-beta",
4
+ "type": "module",
5
+ "description": "Momentum SDK to access the Momentum AMM DeFi protocols suite on Sui.",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsup",
17
+ "dev": "tsup --watch",
18
+ "prepublishOnly": "bun run build",
19
+ "test": "vitest",
20
+ "lint": "eslint src",
21
+ "format": "prettier --write \"src/**/*.{ts,js,json,md}\"",
22
+ "prepare": "husky install"
23
+ },
24
+ "dependencies": {
25
+ "@mysten/sui": "^1.3.0",
26
+ "axios": "^1.4.0"
27
+ },
28
+ "devDependencies": {
29
+ "@commitlint/cli": "^18.4.3",
30
+ "@commitlint/config-conventional": "^18.4.3",
31
+ "@types/node": "^20.14.11",
32
+ "bun-types": "latest",
33
+ "eslint": "^8.56.0",
34
+ "husky": "^8.0.3",
35
+ "prettier": "^3.1.1",
36
+ "tsup": "^8.0.1",
37
+ "typescript": "^5.5.3",
38
+ "vitest": "^1.2.1"
39
+ }
40
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Dex } from './sdk/dex';
2
+
3
+ export * from './utils/types';
4
+ export * from './utils/constants';
5
+ export * from './utils/utils';
6
+
7
+ export { Dex as MmtAmmSdk };
package/src/sdk/dex.ts ADDED
@@ -0,0 +1,423 @@
1
+ import { TransactionArgument, TransactionObjectArgument } from '@mysten/sui/transactions';
2
+ import { DexConstants } from '../utils/constants';
3
+ import { SdkBase } from './sdkBase';
4
+ import {
5
+ Pool,
6
+ SwapOptions,
7
+ SwapWithRouteOptions,
8
+ RemoveLiquidityOptions,
9
+ AddLiquidityOptions,
10
+ AddLiquiditySingleSidedOptions,
11
+ GetUserLpPositionsOptions,
12
+ GetSwapAmountStableOptions,
13
+ GetSwapAmountUncorrelatedOptions,
14
+ GetLpAmountInOptions,
15
+ GetLpAmountOutOptions,
16
+ MergeLpsTxOptions,
17
+ SplitLpsTxOptions,
18
+ CreatePoolOptions,
19
+ CreatePoolAndAddLiquidityOptions,
20
+ SdkOptions,
21
+ } from '../utils/types';
22
+ import {
23
+ bigIntMin,
24
+ fetchCoinPrice,
25
+ getOptimalLpSwapAmount,
26
+ getPriceStable,
27
+ getPriceUncorrelated,
28
+ } from '../utils/utils';
29
+
30
+ export class Dex extends SdkBase {
31
+ constructor(options: SdkOptions | string) {
32
+ super(options);
33
+ }
34
+
35
+ public createPool({
36
+ isStable,
37
+ decimalsX,
38
+ decimalsY,
39
+ txb,
40
+ typeX,
41
+ typeY,
42
+ }: CreatePoolOptions): void {
43
+ txb.moveCall({
44
+ target: `${this.config.packageId}::spot_dex::${DexConstants.functions.createPool}`,
45
+ arguments: [
46
+ txb.object(this.config.protocolConfigs),
47
+ txb.pure.bool(isStable),
48
+ txb.pure.u8(decimalsX),
49
+ txb.pure.u8(decimalsY),
50
+ ],
51
+ typeArguments: [typeX, typeY],
52
+ });
53
+ }
54
+
55
+ public createPoolAndAddLiquidity({
56
+ isStable,
57
+ decimalsX,
58
+ decimalsY,
59
+ txb,
60
+ typeX,
61
+ typeY,
62
+ minAddAmountX,
63
+ minAddAmountY,
64
+ coinX,
65
+ coinY,
66
+ }: CreatePoolAndAddLiquidityOptions) {
67
+ const coinXObject = typeof coinX === 'string' ? txb.object(coinX) : coinX;
68
+ const coinYObject = typeof coinY === 'string' ? txb.object(coinY) : coinY;
69
+
70
+ txb.moveCall({
71
+ target: `${this.config.packageId}::spot_dex::${DexConstants.functions.createPoolAndAddLiquidity}`,
72
+ arguments: [
73
+ txb.object(this.config.protocolConfigs),
74
+ txb.pure.bool(isStable),
75
+ txb.pure.u8(decimalsX),
76
+ txb.pure.u8(decimalsY),
77
+ coinYObject,
78
+ coinXObject,
79
+ txb.pure.u64(minAddAmountY),
80
+ txb.pure.u64(minAddAmountX),
81
+ ],
82
+ typeArguments: [typeX, typeY],
83
+ });
84
+ }
85
+
86
+ public swap({
87
+ pool,
88
+ inputCoinType,
89
+ inputCoinAmount,
90
+ inputCoin,
91
+ minReceived,
92
+ txb,
93
+ transferToAddress,
94
+ }: SwapOptions): TransactionArgument | undefined {
95
+ const isXtoY = pool.tokenXType === inputCoinType;
96
+ const inputCoinObject = typeof inputCoin === 'string' ? txb.object(inputCoin) : inputCoin;
97
+ const inputTokenAmount =
98
+ typeof inputCoinAmount === 'bigint' ? txb.pure.u64(inputCoinAmount) : inputCoinAmount;
99
+
100
+ const txnResult = txb.moveCall({
101
+ target: `${this.config.packageId}::spot_dex::${isXtoY ? DexConstants.functions.swapX : DexConstants.functions.swapY}`,
102
+ arguments: [
103
+ txb.object(pool.objectId),
104
+ inputCoinObject,
105
+ inputTokenAmount,
106
+ txb.pure.u64(minReceived),
107
+ ],
108
+ typeArguments: [pool.tokenXType, pool.tokenYType],
109
+ });
110
+
111
+ if (Boolean(transferToAddress)) {
112
+ txb.transferObjects([txnResult], txb.pure.address(transferToAddress!));
113
+ } else {
114
+ return txnResult;
115
+ }
116
+ }
117
+
118
+ public swapWithRoute({
119
+ pools,
120
+ inputCoinType,
121
+ inputCoinAmount,
122
+ inputCoin,
123
+ minReceived,
124
+ txb,
125
+ transferToAddress,
126
+ }: SwapWithRouteOptions): TransactionArgument | undefined {
127
+ let inputTokenType = inputCoinType;
128
+ let inputToken = inputCoin;
129
+ let inputTokenAmount = inputCoinAmount;
130
+ let txbResultToReturn;
131
+ pools.forEach((pool: Pool, idx) => {
132
+ const isXtoY = pool.tokenXType === inputTokenType;
133
+ const txRes = this.swap({
134
+ pool,
135
+ inputCoinType: inputTokenType,
136
+ inputCoinAmount: inputTokenAmount,
137
+ inputCoin: inputToken,
138
+ minReceived: idx == pools.length - 1 ? minReceived : BigInt(0), // set with zero for non last pools.
139
+ txb,
140
+ });
141
+
142
+ if (!txRes) {
143
+ throw new Error('Swap failed during route execution');
144
+ }
145
+
146
+ inputTokenType = isXtoY ? pool.tokenYType : pool.tokenXType;
147
+ inputToken = txRes;
148
+ inputTokenAmount = this.getCoinValue(inputTokenType, inputToken, txb);
149
+ txbResultToReturn = txRes;
150
+ });
151
+
152
+ if (Boolean(transferToAddress)) {
153
+ txb.transferObjects([txbResultToReturn!], txb.pure.address(transferToAddress!));
154
+ } else {
155
+ return txbResultToReturn;
156
+ }
157
+ }
158
+
159
+ public removeLiquidity({
160
+ pool,
161
+ amount,
162
+ mmtLpToken,
163
+ txb,
164
+ transferToAddress,
165
+ }: RemoveLiquidityOptions): Array<TransactionArgument> | undefined {
166
+ const txRes = txb.moveCall({
167
+ target: `${this.config.packageId}::spot_dex::remove_liquidity`,
168
+ typeArguments: [pool.tokenXType, pool.tokenYType],
169
+ arguments: [
170
+ txb.object(pool.objectId),
171
+ typeof mmtLpToken == 'string' ? txb.object(mmtLpToken) : mmtLpToken,
172
+ typeof amount == 'bigint' ? txb.pure.u64(amount) : amount,
173
+ ],
174
+ });
175
+
176
+ if (Boolean(transferToAddress)) {
177
+ txb.transferObjects([txRes[0]], txb.pure.address(transferToAddress!));
178
+ txb.transferObjects([txRes[1]], txb.pure.address(transferToAddress!));
179
+ } else {
180
+ return txRes;
181
+ }
182
+ }
183
+
184
+ public addLiquidity({
185
+ pool,
186
+ amountX,
187
+ amountY,
188
+ minAddAmountX,
189
+ minAddAmountY,
190
+ coinX,
191
+ coinY,
192
+ txb,
193
+ transferToAddress,
194
+ }: AddLiquidityOptions): TransactionArgument | undefined {
195
+ const [lpObject] = txb.moveCall({
196
+ target: `${this.config.packageId}::spot_dex::add_liquidity`,
197
+ typeArguments: [pool.tokenXType, pool.tokenYType],
198
+ arguments: [
199
+ txb.object(pool.objectId),
200
+ typeof coinY == 'string' ? txb.object(coinY) : coinY,
201
+ typeof coinX == 'string' ? txb.object(coinX) : coinX,
202
+ typeof amountY == 'bigint' ? txb.pure.u64(amountY) : amountY,
203
+ typeof amountX == 'bigint' ? txb.pure.u64(amountX) : amountX,
204
+ txb.pure.u64(minAddAmountY),
205
+ txb.pure.u64(minAddAmountX),
206
+ ],
207
+ });
208
+
209
+ if (Boolean(transferToAddress)) {
210
+ txb.transferObjects([lpObject], txb.pure.address(transferToAddress!));
211
+ } else {
212
+ return lpObject;
213
+ }
214
+ }
215
+
216
+ public async addLiquiditySingleSided({
217
+ pool,
218
+ inputCoinType,
219
+ inputCoinAmount,
220
+ inputCoin,
221
+ tokenInDecimals,
222
+ tokenOutDecimals,
223
+ swapSlippageTolerance,
224
+ txb,
225
+ transferToAddress,
226
+ }: AddLiquiditySingleSidedOptions): Promise<TransactionArgument | null | undefined> {
227
+ const isXtoY = pool.tokenXType === inputCoinType;
228
+ const amountToSwap = await getOptimalLpSwapAmount(
229
+ inputCoinAmount,
230
+ pool.objectId,
231
+ isXtoY,
232
+ this.suiClient,
233
+ pool?.isStable,
234
+ );
235
+
236
+ const coinToSwap = txb.splitCoins(
237
+ typeof inputCoin == 'string'
238
+ ? txb.object(inputCoin)
239
+ : (inputCoin as any as TransactionObjectArgument),
240
+ [typeof amountToSwap == 'bigint' ? txb.pure.u64(amountToSwap) : amountToSwap],
241
+ );
242
+
243
+ const tokenInPrice = await fetchCoinPrice(inputCoinType);
244
+ const tokenOutType = pool.tokenXType === inputCoinType ? pool.tokenYType : pool.tokenXType;
245
+ const tokenOutPrice = await fetchCoinPrice(tokenOutType);
246
+
247
+ const amountInUsd = (Number(amountToSwap) / 10 ** tokenInDecimals) * Number(tokenInPrice);
248
+ const minAmountOutUsd = amountInUsd * (1 - swapSlippageTolerance);
249
+ const minTokenOutAmount = BigInt(
250
+ Math.floor((minAmountOutUsd / Number(tokenOutPrice)) * 10 ** tokenOutDecimals),
251
+ );
252
+
253
+ const swappedCoin = this.swap({
254
+ pool,
255
+ inputCoinType,
256
+ inputCoinAmount: amountToSwap,
257
+ inputCoin: coinToSwap,
258
+ minReceived: minTokenOutAmount,
259
+ txb,
260
+ });
261
+
262
+ if (!swappedCoin) return null;
263
+
264
+ const coinX = isXtoY ? inputCoin : swappedCoin;
265
+ const coinY = isXtoY ? swappedCoin : inputCoin;
266
+ const coinXAmount = this.getCoinValue(pool.tokenXType, coinX, txb);
267
+ const coinYAmount = this.getCoinValue(pool.tokenYType, coinY, txb);
268
+
269
+ return this.addLiquidity({
270
+ pool,
271
+ amountX: coinXAmount,
272
+ amountY: coinYAmount,
273
+ minAddAmountX: BigInt(0),
274
+ minAddAmountY: BigInt(0),
275
+ coinX,
276
+ coinY,
277
+ txb,
278
+ transferToAddress,
279
+ });
280
+ }
281
+
282
+ public async getUserLpPositions({
283
+ pool,
284
+ userAddress,
285
+ maxIter = 5,
286
+ cursor: initialCursor = null,
287
+ }: GetUserLpPositionsOptions) {
288
+ let cursor: string | null | undefined = initialCursor;
289
+ const lpTokenType = `${this.config.packageId}::spot_dex::MmtLPToken<${pool.tokenXType},${pool.tokenYType}>`;
290
+ let data: any[] = [];
291
+ let iter = 0;
292
+ do {
293
+ const res: any = await this.suiClient.getOwnedObjects({
294
+ owner: userAddress,
295
+ filter: {
296
+ StructType: lpTokenType,
297
+ },
298
+ options: {
299
+ showContent: true,
300
+ },
301
+ cursor: cursor as string | null,
302
+ limit: 50,
303
+ });
304
+ data = [...data, ...res.data];
305
+ cursor = res.nextCursor;
306
+ iter++;
307
+ } while (cursor !== null && iter < maxIter);
308
+
309
+ return data
310
+ .filter((item) => (item.data.content as any).fields.pool_id == pool.objectId)
311
+ .map((item) => (item.data.content as any).fields);
312
+ }
313
+
314
+ public getSwapAmountStable({
315
+ reserveA,
316
+ reserveB,
317
+ scaleA,
318
+ scaleB,
319
+ amount,
320
+ totalFeeRate = BigInt(0),
321
+ }: GetSwapAmountStableOptions) {
322
+ let amountToSwapA = amount / BigInt(2);
323
+ let counter = 0;
324
+ let left = BigInt(0),
325
+ right = amount;
326
+ let recievedAmountB = BigInt(0);
327
+ // max 100 iterations for search
328
+ while (counter < 100) {
329
+ recievedAmountB = getPriceStable(
330
+ amountToSwapA,
331
+ reserveA,
332
+ reserveB,
333
+ totalFeeRate,
334
+ scaleA,
335
+ scaleB,
336
+ );
337
+ const userRatio = recievedAmountB / (amount - amountToSwapA);
338
+ // we are adding to reserveA, removing from reserveB
339
+ const reserveRatio =
340
+ (Number(reserveB) - Number(recievedAmountB)) / (Number(reserveA) + Number(amountToSwapA));
341
+
342
+ if (userRatio > reserveRatio) {
343
+ right = amountToSwapA;
344
+ } else {
345
+ left = amountToSwapA;
346
+ }
347
+
348
+ amountToSwapA = (left + right) / BigInt(2);
349
+ counter += 1;
350
+ }
351
+
352
+ return {
353
+ amountToSwap: amountToSwapA,
354
+ recievedAmount: recievedAmountB,
355
+ };
356
+ }
357
+
358
+ public getSwapAmountUncorrelated({
359
+ reserveA,
360
+ reserveB,
361
+ amount,
362
+ totalFeeRate = BigInt(0),
363
+ }: GetSwapAmountUncorrelatedOptions) {
364
+ let amountToSwapA = amount / BigInt(2);
365
+ let counter = 0;
366
+ let left = BigInt(0),
367
+ right = amount;
368
+ let recievedAmountB = BigInt(0);
369
+ while (counter < 100) {
370
+ recievedAmountB = getPriceUncorrelated(amountToSwapA, reserveA, reserveB, totalFeeRate);
371
+ const userRatio = recievedAmountB / (amount - amountToSwapA);
372
+ const reserveRatio =
373
+ (Number(reserveB) - Number(recievedAmountB)) / (Number(reserveA) + Number(amountToSwapA));
374
+
375
+ if (userRatio > reserveRatio) {
376
+ right = amountToSwapA;
377
+ } else {
378
+ left = amountToSwapA;
379
+ }
380
+
381
+ amountToSwapA = (left + right) / BigInt(2);
382
+ counter += 1;
383
+ }
384
+
385
+ return {
386
+ amountToSwap: amountToSwapA,
387
+ recievedAmount: recievedAmountB,
388
+ };
389
+ }
390
+
391
+ public getLpAmountIn({ amountX, amountY, lspSupply, reserveX, reserveY }: GetLpAmountInOptions) {
392
+ return bigIntMin([(amountX * lspSupply) / reserveX, (amountY * lspSupply) / reserveY]);
393
+ }
394
+
395
+ public getLpAmountOut({ lspAmount, lspSupply, reserveX, reserveY }: GetLpAmountOutOptions) {
396
+ return {
397
+ tokenX: reserveX * (lspAmount / lspSupply),
398
+ tokenY: reserveY * (lspAmount / lspSupply),
399
+ };
400
+ }
401
+
402
+ public mergeLpsTx = ({ txb, lps, typeX, typeY }: MergeLpsTxOptions) => {
403
+ if (lps.length > 1) {
404
+ lps.slice(1).forEach((lp) => {
405
+ txb.moveCall({
406
+ target: `${this.config.packageIdV1}::spot_dex::lp_token_join`,
407
+ arguments: [txb.object(lps[0]), txb.object(lp)],
408
+ typeArguments: [typeX, typeY],
409
+ });
410
+ });
411
+ }
412
+ };
413
+
414
+ public splitLpsTx = ({ txb, lp, amount, typeX, typeY }: SplitLpsTxOptions) => {
415
+ const [splitToken] = txb.moveCall({
416
+ target: `${this.config.packageIdV1}::spot_dex::lp_token_split`,
417
+ arguments: [typeof lp == 'string' ? txb.object(lp) : lp, txb.pure.u64(amount)],
418
+ typeArguments: [typeX, typeY],
419
+ });
420
+
421
+ return splitToken;
422
+ };
423
+ }
@@ -0,0 +1,199 @@
1
+ import {
2
+ TransactionArgument,
3
+ Transaction,
4
+ TransactionObjectArgument,
5
+ } from '@mysten/sui/transactions';
6
+ import { CoinStruct, SuiClient } from '@mysten/sui/client';
7
+ import { DexConstants, NETWORK_CONFIG } from '../utils/constants';
8
+ import { GetExactCoinByAmountOptions, SdkOptions, Network } from '../utils/types';
9
+
10
+ class SdkBase {
11
+ suiClient: SuiClient;
12
+ network: Network;
13
+ config: typeof NETWORK_CONFIG.mainnet;
14
+
15
+ constructor(options: SdkOptions | string) {
16
+ if (typeof options === 'string') {
17
+ this.suiClient = new SuiClient({ url: options });
18
+ this.network = 'mainnet';
19
+ this.config = NETWORK_CONFIG.mainnet;
20
+ } else {
21
+ this.suiClient = new SuiClient({ url: options.rpcEndpoint });
22
+ this.network = options.network ?? 'mainnet';
23
+ this.config = NETWORK_CONFIG[this.network];
24
+ }
25
+ }
26
+
27
+ public getSuiCoin(amount: bigint | TransactionArgument, txb: Transaction): TransactionArgument {
28
+ const inputCoinAmount = typeof amount === 'bigint' ? txb.pure.u64(amount) : amount;
29
+ const [coin] = txb.splitCoins(txb.gas, [inputCoinAmount]);
30
+ return coin;
31
+ }
32
+
33
+ public mergeCoins(
34
+ coinObjects: Array<string | TransactionArgument>,
35
+ txb: Transaction,
36
+ ): TransactionArgument | undefined {
37
+ if (coinObjects.length == 1) {
38
+ return typeof coinObjects[0] == 'string' ? txb.object(coinObjects[0]) : coinObjects[0];
39
+ }
40
+ let firstCoin =
41
+ typeof coinObjects[0] == 'string'
42
+ ? txb.object(coinObjects[0])
43
+ : (coinObjects[0] as any as TransactionObjectArgument);
44
+ txb.mergeCoins(
45
+ firstCoin,
46
+ coinObjects
47
+ .slice(1)
48
+ .map((coin) =>
49
+ typeof coin == 'string' ? txb.object(coin) : (coin as any as TransactionObjectArgument),
50
+ ),
51
+ );
52
+ return firstCoin;
53
+ }
54
+
55
+ public getCoinValue(
56
+ coinType: string,
57
+ coinObject: string | TransactionArgument,
58
+ txb: Transaction,
59
+ ): TransactionArgument {
60
+ const inputCoinObject = typeof coinObject == 'string' ? txb.object(coinObject) : coinObject;
61
+ let [value] = txb.moveCall({
62
+ target: `0x2::coin::value`,
63
+ typeArguments: [coinType],
64
+
65
+ arguments: [inputCoinObject],
66
+ });
67
+ return value;
68
+ }
69
+
70
+ public getExactCoinByAmount({ coinType, coins, amount, txb }: GetExactCoinByAmountOptions) {
71
+ if (coinType === DexConstants.suiCoinType) {
72
+ const [coinA] = txb.splitCoins(txb.gas, [txb.pure.u64(amount)]);
73
+ return coinA;
74
+ } else {
75
+ const coinsX = this.getCoinsGreaterThanAmount(amount, coins);
76
+
77
+ if (coinsX.length > 1) {
78
+ txb.mergeCoins(
79
+ txb.object(coinsX[0]),
80
+ coinsX.slice(1).map((coin) => txb.object(coin)),
81
+ );
82
+ }
83
+
84
+ const [coinA] = txb.splitCoins(txb.object(coinsX[0]), [txb.pure.u64(amount)]);
85
+ return coinA;
86
+ }
87
+ }
88
+
89
+ public mergeAllUserCoins = async (coinType: string, signerAddress: string) => {
90
+ try {
91
+ const coins = await this.getAllUserCoins({
92
+ address: signerAddress,
93
+ type: coinType,
94
+ });
95
+
96
+ let totalBalance = BigInt(0);
97
+
98
+ coins.forEach((coin) => {
99
+ totalBalance += BigInt(coin.balance);
100
+ });
101
+
102
+ const txb = new Transaction();
103
+
104
+ if (coinType === DexConstants.suiCoinType) {
105
+ totalBalance = totalBalance - BigInt('1000');
106
+ txb.splitCoins(txb.gas, [txb.pure.u64(totalBalance)]);
107
+ }
108
+
109
+ const coinObjectsIds = coins.map((coin) => coin.coinObjectId);
110
+
111
+ if (coins.length > 1) {
112
+ txb.mergeCoins(
113
+ txb.object(coinObjectsIds[0]),
114
+ coinObjectsIds.slice(1).map((coin) => txb.object(coin)),
115
+ );
116
+ }
117
+
118
+ return txb;
119
+ } catch (error) {
120
+ console.log(error);
121
+ }
122
+ };
123
+
124
+ public mergeAllCoinsWithoutFetch(coins: CoinStruct[], coinType: string, txb: Transaction) {
125
+ let totalBalance = BigInt(0);
126
+ coins.forEach((coin) => {
127
+ totalBalance += BigInt(coin.balance);
128
+ });
129
+
130
+ if (coinType === DexConstants.suiCoinType) {
131
+ totalBalance = totalBalance - BigInt('1000');
132
+ txb.splitCoins(txb.gas, [txb.pure.u64(totalBalance)]);
133
+ }
134
+
135
+ const coinObjectsIds = coins.map((coin) => coin.coinObjectId);
136
+
137
+ if (coins.length > 1) {
138
+ txb.mergeCoins(
139
+ txb.object(coinObjectsIds[0]),
140
+ coinObjectsIds.slice(1).map((coin) => txb.object(coin)),
141
+ );
142
+ }
143
+ }
144
+
145
+ public getAllUserCoins = async ({ address, type }: { type: string; address: string }) => {
146
+ let cursor: string | null | undefined;
147
+
148
+ let coins: CoinStruct[] = [];
149
+ let iter = 0;
150
+
151
+ do {
152
+ try {
153
+ const res = await this.suiClient.getCoins({
154
+ owner: address,
155
+ coinType: type,
156
+ cursor: cursor,
157
+ limit: 50,
158
+ });
159
+ coins = coins.concat(res.data);
160
+ cursor = res.nextCursor;
161
+ if (!res.hasNextPage || iter === 8) {
162
+ cursor = null;
163
+ }
164
+ } catch (error) {
165
+ console.log(error);
166
+ cursor = null;
167
+ }
168
+ iter++;
169
+ } while (cursor !== null && cursor !== undefined);
170
+
171
+ return coins;
172
+ };
173
+
174
+ private getCoinsGreaterThanAmount = (
175
+ amount: bigint,
176
+ coins: { objectId: string; balance: bigint }[],
177
+ ) => {
178
+ const coinsWithBalance: string[] = [];
179
+
180
+ let collectedAmount = BigInt(0);
181
+
182
+ for (const coin of coins) {
183
+ if (collectedAmount < amount && !coinsWithBalance.includes(coin.objectId)) {
184
+ coinsWithBalance.push(coin.objectId);
185
+ collectedAmount = collectedAmount + coin.balance;
186
+ }
187
+ if (coin.balance === BigInt(0) && !coinsWithBalance.includes(coin.objectId))
188
+ coinsWithBalance.push(coin.objectId);
189
+ }
190
+
191
+ if (collectedAmount >= amount) {
192
+ return coinsWithBalance;
193
+ } else {
194
+ throw new Error('Insufficient balance');
195
+ }
196
+ };
197
+ }
198
+
199
+ export { SdkBase };