@scallop-io/sui-scallop-sdk 2.0.12 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scallop-io/sui-scallop-sdk",
3
- "version": "2.0.12",
3
+ "version": "2.0.13-merge-split-ve-sca-alpha.1",
4
4
  "description": "Typescript sdk for interacting with Scallop contract on SUI",
5
5
  "keywords": [
6
6
  "sui",
@@ -64,6 +64,9 @@
64
64
  "@types/node": "^20.4.2",
65
65
  "@typescript-eslint/eslint-plugin": "^8.11.0",
66
66
  "@typescript-eslint/parser": "8.10.0",
67
+ "@vitest/expect": "^3.1.1",
68
+ "@vitest/runner": "^3.1.1",
69
+ "@vitest/spy": "^3.1.1",
67
70
  "dotenv": "^16.3.1",
68
71
  "eslint": "^8.53.0",
69
72
  "eslint-config-prettier": "^9.0.0",
@@ -77,7 +80,7 @@
77
80
  "tsup": "^7.2.0",
78
81
  "typedoc": "^0.26.3",
79
82
  "typescript": "5.5.4",
80
- "vitest": "^0.34.6"
83
+ "vitest": "^3.1.1"
81
84
  },
82
85
  "peerDependencies": {
83
86
  "@mysten/bcs": "^1.2.0",
@@ -3,8 +3,8 @@ import {
3
3
  SuiTxBlock as SuiKitTxBlock,
4
4
  SUI_CLOCK_OBJECT_ID,
5
5
  } from '@scallop-io/sui-kit';
6
- import { getObligations, getObligationLocked } from '../queries';
7
- import { requireSender } from '../utils';
6
+ import { getObligations, getObligationLocked } from 'src/queries';
7
+ import { requireSender } from 'src/utils';
8
8
  import type { SuiObjectArg } from '@scallop-io/sui-kit';
9
9
  import type { ScallopBuilder } from 'src/models';
10
10
  import type {
@@ -14,8 +14,7 @@ import type {
14
14
  SuiTxBlockWithBorrowIncentiveNormalMethods,
15
15
  BorrowIncentiveTxBlock,
16
16
  ScallopTxBlock,
17
- VescaIds,
18
- } from '../types';
17
+ } from 'src/types';
19
18
  import { OLD_BORROW_INCENTIVE_PROTOCOL_ID } from 'src/constants';
20
19
 
21
20
  /**
@@ -92,7 +91,7 @@ const generateBorrowIncentiveNormalMethod: GenerateBorrowIncentiveNormalMethod =
92
91
  obligationAccessStore: builder.address.get('core.obligationAccessStore'),
93
92
  };
94
93
 
95
- const veScaIds: Omit<VescaIds, 'pkgId'> = {
94
+ const veScaIds = {
96
95
  table: builder.address.get('vesca.table'),
97
96
  treasury: builder.address.get('vesca.treasury'),
98
97
  config: builder.address.get('vesca.config'),
@@ -5,7 +5,7 @@ import { getObligations } from '../queries';
5
5
  import { updateOracles } from './oracles';
6
6
  import { requireSender } from '../utils';
7
7
  import type { SuiObjectArg, TransactionResult } from '@scallop-io/sui-kit';
8
- import type { ScallopBuilder } from '../models';
8
+ import type { ScallopBuilder } from 'src/models';
9
9
  import type {
10
10
  CoreIds,
11
11
  GenerateCoreNormalMethod,
@@ -15,7 +15,7 @@ import type {
15
15
  ScallopTxBlock,
16
16
  NestedResult,
17
17
  SuiTxBlockWithSpool,
18
- } from '../types';
18
+ } from 'src/types';
19
19
 
20
20
  /**
21
21
  * Check and get Obligation information from transaction block.
@@ -4,8 +4,8 @@ import { newCoreTxBlock } from './coreBuilder';
4
4
  import { newSpoolTxBlock } from './spoolBuilder';
5
5
  import { newBorrowIncentiveTxBlock } from './borrowIncentiveBuilder';
6
6
  import { newVeScaTxBlock } from './vescaBuilder';
7
- import type { ScallopBuilder } from '../models';
8
- import type { ScallopTxBlock } from '../types';
7
+ import type { ScallopBuilder } from 'src/models';
8
+ import type { ScallopTxBlock } from 'src/types';
9
9
  import { newReferralTxBlock } from './referralBuilder';
10
10
  import { newLoyaltyProgramTxBlock } from './loyaltyProgramBuilder';
11
11
  import { newSCoinTxBlock } from './sCoinBuilder';
@@ -5,7 +5,7 @@ import { getStakeAccounts } from '../queries/spoolQuery';
5
5
  import { requireSender } from '../utils';
6
6
  import type { SuiAddressArg } from '@scallop-io/sui-kit';
7
7
  import type { TransactionResult } from '@mysten/sui/transactions';
8
- import type { ScallopBuilder } from '../models';
8
+ import type { ScallopBuilder } from 'src/models';
9
9
  import type {
10
10
  SpoolIds,
11
11
  GenerateSpoolNormalMethod,
@@ -14,7 +14,7 @@ import type {
14
14
  SpoolTxBlock,
15
15
  ScallopTxBlock,
16
16
  SuiTxBlockWithSCoin,
17
- } from '../types';
17
+ } from 'src/types';
18
18
 
19
19
  /**
20
20
  * Check and get stake account id from transaction block.
@@ -5,8 +5,8 @@ import {
5
5
  SuiTxBlock as SuiKitTxBlock,
6
6
  } from '@scallop-io/sui-kit';
7
7
  import { SCA_COIN_TYPE } from 'src/constants';
8
- import { ScallopBuilder } from '../models';
9
- import { getVeSca, getVeScas } from '../queries';
8
+ import { ScallopBuilder } from 'src/models';
9
+ import { getVeSca, getVeScas } from 'src/queries';
10
10
  import {
11
11
  requireSender,
12
12
  checkLockSca,
@@ -14,19 +14,19 @@ import {
14
14
  checkExtendLockAmount,
15
15
  checkRenewExpiredVeSca,
16
16
  checkVesca,
17
- } from '../utils';
17
+ } from 'src/utils';
18
18
  import type {
19
19
  TransactionObjectArgument,
20
20
  SuiObjectArg,
21
21
  } from '@scallop-io/sui-kit';
22
22
  import type {
23
+ AddressesInterface,
23
24
  GenerateVeScaNormalMethod,
24
25
  GenerateVeScaQuickMethod,
25
- RedeemScaQuickReturnType,
26
+ QuickMethodReturnType,
26
27
  ScallopTxBlock,
27
28
  SuiTxBlockWithVeScaNormalMethods,
28
29
  VeScaTxBlock,
29
- VescaIds,
30
30
  } from 'src/types';
31
31
 
32
32
  /**
@@ -70,6 +70,27 @@ export const requireVeSca = async (
70
70
  return veScaKey ? veScas.find(({ keyId }) => veScaKey === keyId) : veScas[0];
71
71
  };
72
72
 
73
+ export const isInSubsTable = async (
74
+ ...params: [builder: ScallopBuilder, veScaKey: string, tableId: string]
75
+ ) => {
76
+ const [builder, veScaKey, tableId] = params;
77
+ try {
78
+ const _resp = await builder.cache.queryGetDynamicFieldObject({
79
+ parentId: tableId,
80
+ name: {
81
+ type: '0x2::object::ID',
82
+ value: veScaKey,
83
+ },
84
+ });
85
+ return true;
86
+ } catch (e) {
87
+ console.error(e);
88
+ return false;
89
+ }
90
+ };
91
+
92
+ type VeScaProps = 'id' | 'table' | 'treasury' | 'config' | 'subsTable';
93
+
73
94
  /**
74
95
  * Generate veSCA normal methods.
75
96
  *
@@ -81,12 +102,14 @@ const generateNormalVeScaMethod: GenerateVeScaNormalMethod = ({
81
102
  builder,
82
103
  txBlock,
83
104
  }) => {
84
- const veScaIds: VescaIds = {
85
- pkgId: builder.address.get('vesca.id'),
105
+ const veScaIds: Pick<AddressesInterface['vesca'], VeScaProps> = {
106
+ id: builder.address.get('vesca.id'),
86
107
  table: builder.address.get('vesca.table'),
87
108
  treasury: builder.address.get('vesca.treasury'),
88
109
  config: builder.address.get('vesca.config'),
110
+ subsTable: builder.address.get('vesca.subsTable'),
89
111
  };
112
+
90
113
  const clockObjectRef = txBlock.sharedObjectRef({
91
114
  objectId: SUI_CLOCK_OBJECT_ID,
92
115
  mutable: false,
@@ -97,7 +120,7 @@ const generateNormalVeScaMethod: GenerateVeScaNormalMethod = ({
97
120
  lockSca: (scaCoin, unlockAtInSecondTimestamp) => {
98
121
  return builder.moveCall(
99
122
  txBlock,
100
- `${veScaIds.pkgId}::ve_sca::mint_ve_sca_key`,
123
+ `${veScaIds.id}::ve_sca::mint_ve_sca_key`,
101
124
  [
102
125
  veScaIds.config,
103
126
  veScaIds.table,
@@ -112,7 +135,7 @@ const generateNormalVeScaMethod: GenerateVeScaNormalMethod = ({
112
135
  extendLockPeriod: (veScaKey, newUnlockAtInSecondTimestamp) => {
113
136
  builder.moveCall(
114
137
  txBlock,
115
- `${veScaIds.pkgId}::ve_sca::extend_lock_period`,
138
+ `${veScaIds.id}::ve_sca::extend_lock_period`,
116
139
  [
117
140
  veScaIds.config,
118
141
  veScaKey,
@@ -127,7 +150,7 @@ const generateNormalVeScaMethod: GenerateVeScaNormalMethod = ({
127
150
  extendLockAmount: (veScaKey, scaCoin) => {
128
151
  builder.moveCall(
129
152
  txBlock,
130
- `${veScaIds.pkgId}::ve_sca::lock_more_sca`,
153
+ `${veScaIds.id}::ve_sca::lock_more_sca`,
131
154
  [
132
155
  veScaIds.config,
133
156
  veScaKey,
@@ -142,7 +165,7 @@ const generateNormalVeScaMethod: GenerateVeScaNormalMethod = ({
142
165
  renewExpiredVeSca: (veScaKey, scaCoin, newUnlockAtInSecondTimestamp) => {
143
166
  builder.moveCall(
144
167
  txBlock,
145
- `${veScaIds.pkgId}::ve_sca::renew_expired_ve_sca`,
168
+ `${veScaIds.id}::ve_sca::renew_expired_ve_sca`,
146
169
  [
147
170
  veScaIds.config,
148
171
  veScaKey,
@@ -158,7 +181,7 @@ const generateNormalVeScaMethod: GenerateVeScaNormalMethod = ({
158
181
  redeemSca: (veScaKey) => {
159
182
  return builder.moveCall(
160
183
  txBlock,
161
- `${veScaIds.pkgId}::ve_sca::redeem`,
184
+ `${veScaIds.id}::ve_sca::redeem`,
162
185
  [
163
186
  veScaIds.config,
164
187
  veScaKey,
@@ -172,11 +195,39 @@ const generateNormalVeScaMethod: GenerateVeScaNormalMethod = ({
172
195
  mintEmptyVeSca: () => {
173
196
  return builder.moveCall(
174
197
  txBlock,
175
- `${veScaIds.pkgId}::ve_sca::mint_ve_sca_placeholder_key`,
198
+ `${veScaIds.id}::ve_sca::mint_ve_sca_placeholder_key`,
176
199
  [veScaIds.config, veScaIds.table],
177
200
  []
178
201
  );
179
202
  },
203
+ splitVeSca: (veScaKey, splitAmount) => {
204
+ return builder.moveCall(txBlock, `${veScaIds.id}::ve_sca::split`, [
205
+ veScaIds.config,
206
+ veScaKey,
207
+ veScaIds.table,
208
+ veScaIds.subsTable,
209
+ txBlock.pure.u64(splitAmount),
210
+ ]);
211
+ },
212
+ mergeVeSca: (targetKey, sourceKey) => {
213
+ return builder.moveCall(
214
+ txBlock,
215
+ `${veScaIds.id}::ve_sca::merge`,
216
+ [
217
+ veScaIds.config,
218
+ targetKey,
219
+ sourceKey,
220
+ veScaIds.table,
221
+ veScaIds.subsTable,
222
+ txBlock.sharedObjectRef({
223
+ objectId: SUI_CLOCK_OBJECT_ID,
224
+ mutable: false,
225
+ initialSharedVersion: '1',
226
+ }),
227
+ ],
228
+ []
229
+ );
230
+ },
180
231
  };
181
232
  };
182
233
 
@@ -362,9 +413,57 @@ const generateQuickVeScaMethod: GenerateVeScaQuickMethod = ({
362
413
  txBlock.transferObjects([sca], sender);
363
414
  return;
364
415
  }
365
- return sca as RedeemScaQuickReturnType<S>;
416
+ return sca as QuickMethodReturnType<S>;
417
+ }
418
+ },
419
+ splitVeScaQuick: async <S extends boolean>(
420
+ splitAmount: string,
421
+ veScaKey: string,
422
+ transferVeScaKey: S = true as S
423
+ ) => {
424
+ const isKeyInSubTable = await isInSubsTable(
425
+ builder,
426
+ veScaKey,
427
+ builder.address.get('vesca.subsTable')
428
+ );
429
+ if (isKeyInSubTable) {
430
+ throw new Error(
431
+ 'Key cannot be in the subs table, please call unsubscribe vesca or unstake obligation first'
432
+ );
433
+ }
434
+
435
+ const newVeScaKey = txBlock.splitVeSca(veScaKey, splitAmount);
436
+ // txBlock.addMergeSplitSub(newVeScaKey);
437
+ if (transferVeScaKey) {
438
+ txBlock.transferObjects(newVeScaKey, requireSender(txBlock));
439
+ return;
440
+ } else {
441
+ return newVeScaKey as QuickMethodReturnType<S>;
366
442
  }
367
443
  },
444
+ mergeVeScaQuick: async (targetKey: string, sourceKey: string) => {
445
+ // check targetKey and sourceKey
446
+ const [isTargetInSubTable, isSourceInSubTable] = await Promise.all([
447
+ isInSubsTable(
448
+ builder,
449
+ targetKey,
450
+ builder.address.get('vesca.subsTable')
451
+ ),
452
+ isInSubsTable(
453
+ builder,
454
+ sourceKey,
455
+ builder.address.get('vesca.subsTable')
456
+ ),
457
+ ]);
458
+
459
+ if (isTargetInSubTable || isSourceInSubTable) {
460
+ throw new Error(
461
+ 'Both target and source cannot be in the subs table. Please call unsubscribe vesca or unstake obligation first'
462
+ );
463
+ }
464
+
465
+ return txBlock.mergeVeSca(targetKey, sourceKey);
466
+ },
368
467
  };
369
468
  };
370
469
 
@@ -256,18 +256,24 @@ export const TEST_ADDRESSES: AddressesInterface = {
256
256
  '0x9636e7b947b806b9fe438d037f02bb24026c5b2691d2f6bad349c2e117f77cc3',
257
257
  },
258
258
  vesca: {
259
- id: '0x1158813b32962c2d22888fae257d5f2365b03631f0cd5d5b912ccdf51ff4e2f2',
259
+ id: '0x0c7f5568dbd69488437ee95f2d9a028724e1de12432965ff8acca7c67310ba46',
260
260
  object:
261
- '0xcfe2d87aa5712b67cad2732edb6a2201bfdf592377e5c0968b7cb02099bd8e21',
261
+ '0x0c7f5568dbd69488437ee95f2d9a028724e1de12432965ff8acca7c67310ba46',
262
262
  adminCap:
263
263
  '0x4d105b16467acca81d18c132cdd1a3cee159920a86c1ef4bdbf2e8d7878500c5',
264
264
  tableId:
265
- '0x0a0b7f749baeb61e3dfee2b42245e32d0e6b484063f0a536b33e771d573d7246',
266
- table: '0xd3a4632b1080f7d96e1c2487d4dabf2c1196916937c505a69954ac9f393be8d0',
265
+ '0x06f763060ea5da3d639fb56df70674490a8354511cfe61584062aafd83b1940d',
266
+ table: '0x06f763060ea5da3d639fb56df70674490a8354511cfe61584062aafd83b1940d',
267
267
  treasury:
268
- '0xafa4b6231e49c15a22d641ce33fda761baaf650fa21899dfa2eb1716146e7306',
268
+ '0x934919cc31fa89b67578039bb10d5518fa23c50bc8f78500f1d1a718407a0a71',
269
269
  config:
270
- '0x7cbcb0a342179577a117dfdff974cf1ab765d3b571067bf22ddf5f9e3a667922',
270
+ '0x38d3f7a1fa5071226535d4d8bfca8ccab3d24871402df1be669d7d5e9e3e9cb4',
271
+ subsTable:
272
+ '0x4756b716670ff62760b22bebed73c6eb2c2cb118674a2eea3a56ebea9e27ae76',
273
+ subsTableId:
274
+ '0x924b56d383b45445984a80002185b670aa2e72cd7df496d345f45f9407a12c07',
275
+ subsWhitelist:
276
+ '0xfc72adae643da4f2fe080adc1e2cca981eadcb518facb02324eeaab169752ffb',
271
277
  },
272
278
  referral: {
273
279
  id: '0x1bf5a8ce77050d8052549d743e16b469f15aa6b81b752b78b6ebb65179665f5a',
@@ -11,7 +11,7 @@ import type {
11
11
  ScallopParams,
12
12
  ScallopQueryParams,
13
13
  ScallopUtilsParams,
14
- } from '../types/';
14
+ } from 'src/types';
15
15
  import { ScallopIndexer } from './scallopIndexer';
16
16
  import { ScallopCache } from './scallopCache';
17
17
  import { QueryClientConfig } from '@tanstack/query-core';
@@ -1,11 +1,11 @@
1
- import { API_BASE_URL, USE_TEST_ADDRESS } from '../constants';
1
+ import { API_BASE_URL, USE_TEST_ADDRESS } from 'src/constants';
2
2
  import { type NetworkType } from '@scallop-io/sui-kit';
3
3
  import type {
4
4
  ScallopAddressParams,
5
5
  AddressesInterface,
6
6
  AddressStringPath,
7
7
  ScallopAddressInstanceParams,
8
- } from '../types';
8
+ } from 'src/types';
9
9
  import { ScallopCache } from './scallopCache';
10
10
  import axios, { AxiosInstance } from 'axios';
11
11
  import { TEST_ADDRESSES } from 'src/constants/testAddress';
@@ -335,6 +335,9 @@ const EMPTY_ADDRESSES: AddressesInterface = {
335
335
  table: '',
336
336
  treasury: '',
337
337
  config: '',
338
+ subsTable: '',
339
+ subsTableId: '',
340
+ subsWhitelist: '',
338
341
  },
339
342
  referral: {
340
343
  id: '',
@@ -21,7 +21,7 @@ import type {
21
21
  ScallopTxBlock,
22
22
  ScallopBuilderInstanceParams,
23
23
  SelectCoinReturnType,
24
- } from '../types';
24
+ } from 'src/types';
25
25
  import { ScallopCache } from './scallopCache';
26
26
  import { newSuiKit } from './suiKit';
27
27
  import { ScallopConstants } from './scallopConstants';
@@ -55,7 +55,7 @@ const deepMergeObject = <T>(curr: T, update: T): T => {
55
55
  return result;
56
56
  };
57
57
 
58
- class RateLimiter {
58
+ export class RateLimiter {
59
59
  private tokens: number;
60
60
  private lastRefillTime: number;
61
61
  private readonly refillRate: number; // tokens per millisecond
@@ -18,7 +18,7 @@ import type {
18
18
  ScallopTxBlock,
19
19
  ScallopClientVeScaReturnType,
20
20
  ScallopClientInstanceParams,
21
- } from '../types';
21
+ } from 'src/types';
22
22
  import { newSuiKit } from './suiKit';
23
23
  import { ScallopConstants } from './scallopConstants';
24
24
 
@@ -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.fromEntries(
391
- Object.entries(whitelistResponse)
392
- .filter(([_, value]) => Array.isArray(value) || value instanceof Set)
393
- .map(([key, value]) => [
394
- key as keyof Whitelist,
395
- value instanceof Set ? value : new Set(value),
396
- ])
397
- ) as Whitelist;
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)
@@ -1,5 +1,5 @@
1
1
  import axios, { AxiosInstance } from 'axios';
2
- import { SDK_API_BASE_URL } from '../constants';
2
+ import { SDK_API_BASE_URL } from 'src/constants';
3
3
  import type {
4
4
  Market,
5
5
  MarketPools,
@@ -3,8 +3,8 @@ import {
3
3
  parseOriginBorrowIncentivePoolData,
4
4
  parseOriginBorrowIncentiveAccountData,
5
5
  calculateBorrowIncentivePoolPointData,
6
- } from '../utils';
7
- import type { ScallopAddress, ScallopQuery, ScallopUtils } from '../models';
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 '../types';
17
+ } from 'src/types';
18
18
  import BigNumber from 'bignumber.js';
19
19
  import { SuiObjectRef } from '@mysten/sui/client';
20
20