@scallop-io/sui-scallop-sdk 0.44.17 → 0.44.19

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 (56) hide show
  1. package/dist/builders/borrowIncentiveBuilder.d.ts +7 -0
  2. package/dist/builders/vescaBuilder.d.ts +24 -0
  3. package/dist/constants/common.d.ts +7 -4
  4. package/dist/constants/index.d.ts +1 -0
  5. package/dist/constants/vesca.d.ts +5 -0
  6. package/dist/index.js +878 -255
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +867 -248
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/models/scallopClient.d.ts +7 -7
  11. package/dist/models/scallopUtils.d.ts +16 -14
  12. package/dist/queries/index.d.ts +1 -0
  13. package/dist/queries/vescaQuery.d.ts +28 -0
  14. package/dist/types/address.d.ts +9 -0
  15. package/dist/types/builder/borrowIncentive.d.ts +9 -6
  16. package/dist/types/builder/index.d.ts +3 -1
  17. package/dist/types/builder/vesca.d.ts +33 -0
  18. package/dist/types/constant/enum.d.ts +1 -1
  19. package/dist/types/query/borrowIncentive.d.ts +65 -60
  20. package/dist/types/query/index.d.ts +1 -0
  21. package/dist/types/query/portfolio.d.ts +12 -6
  22. package/dist/types/query/vesca.d.ts +7 -0
  23. package/dist/types/utils.d.ts +1 -2
  24. package/dist/utils/builder.d.ts +6 -0
  25. package/dist/utils/query.d.ts +4 -10
  26. package/dist/utils/util.d.ts +7 -0
  27. package/package.json +1 -1
  28. package/src/builders/borrowIncentiveBuilder.ts +174 -25
  29. package/src/builders/index.ts +6 -2
  30. package/src/builders/vescaBuilder.ts +392 -0
  31. package/src/constants/common.ts +19 -6
  32. package/src/constants/enum.ts +9 -3
  33. package/src/constants/index.ts +1 -0
  34. package/src/constants/vesca.ts +7 -0
  35. package/src/models/scallopAddress.ts +9 -1
  36. package/src/models/scallopClient.ts +29 -20
  37. package/src/models/scallopUtils.ts +45 -0
  38. package/src/queries/borrowIncentiveQuery.ts +93 -83
  39. package/src/queries/coreQuery.ts +19 -20
  40. package/src/queries/index.ts +1 -0
  41. package/src/queries/portfolioQuery.ts +79 -41
  42. package/src/queries/spoolQuery.ts +1 -1
  43. package/src/queries/vescaQuery.ts +124 -0
  44. package/src/types/address.ts +9 -0
  45. package/src/types/builder/borrowIncentive.ts +22 -5
  46. package/src/types/builder/index.ts +4 -1
  47. package/src/types/builder/vesca.ts +73 -0
  48. package/src/types/constant/enum.ts +1 -1
  49. package/src/types/query/borrowIncentive.ts +195 -74
  50. package/src/types/query/index.ts +1 -0
  51. package/src/types/query/portfolio.ts +17 -6
  52. package/src/types/query/vesca.ts +7 -0
  53. package/src/types/utils.ts +1 -1
  54. package/src/utils/builder.ts +141 -0
  55. package/src/utils/query.ts +227 -131
  56. package/src/utils/util.ts +28 -0
@@ -13,14 +13,20 @@ import type {
13
13
  SuiTxBlockWithBorrowIncentiveNormalMethods,
14
14
  BorrowIncentiveTxBlock,
15
15
  ScallopTxBlock,
16
+ VescaIds,
16
17
  } from '../types';
18
+ import { requireVeSca } from './vescaBuilder';
19
+ import {
20
+ IS_VE_SCA_TEST,
21
+ OLD_BORROW_INCENTIVE_PROTOCOL_ID,
22
+ } from 'src/constants';
17
23
 
18
24
  /**
19
25
  * Check and get Obligation information from transaction block.
20
26
  *
21
27
  * @description
22
- * If the obligation id is provided, direactly return it.
23
- * If both obligation id and key is provided, direactly return them.
28
+ * If the obligation id is provided, directly return it.
29
+ * If both obligation id and key is provided, directly return them.
24
30
  * Otherwise, automatically get obligation id and key from the sender.
25
31
  *
26
32
  * @param builder - Scallop builder instance.
@@ -62,6 +68,59 @@ const requireObligationInfo = async (
62
68
  };
63
69
  };
64
70
 
71
+ /**
72
+ * Check veSca bind status
73
+ * @param query
74
+ * @param veScaKey
75
+ * @returns
76
+ */
77
+ export const getBindedObligationId = async (
78
+ builder: ScallopBuilder,
79
+ veScaKey: string
80
+ ) => {
81
+ const borrowIncentivePkgId = builder.address.get('borrowIncentive.id');
82
+ const incentivePoolsId = builder.address.get(
83
+ 'borrowIncentive.incentivePools'
84
+ );
85
+ const veScaPkgId = IS_VE_SCA_TEST
86
+ ? '0xb220d034bdf335d77ae5bfbf6daf059c2cc7a1f719b12bfed75d1736fac038c8'
87
+ : builder.address.get('vesca.id');
88
+
89
+ const client = builder.suiKit.client();
90
+
91
+ // get incentive pools
92
+ const incentivePoolsResponse = await client.getObject({
93
+ id: incentivePoolsId,
94
+ options: {
95
+ showContent: true,
96
+ },
97
+ });
98
+
99
+ if (incentivePoolsResponse.data?.content?.dataType !== 'moveObject')
100
+ return false;
101
+ const incentivePoolFields = incentivePoolsResponse.data.content.fields as any;
102
+ const veScaBindTableId = incentivePoolFields.ve_sca_bind.fields.id
103
+ .id as string;
104
+
105
+ // check if veSca is inside the bind table
106
+ const keyType = `${borrowIncentivePkgId}::typed_id::TypedID<${veScaPkgId}::ve_sca::VeScaKey>`;
107
+ const veScaBindTableResponse = await client.getDynamicFieldObject({
108
+ parentId: veScaBindTableId,
109
+ name: {
110
+ type: keyType,
111
+ value: veScaKey,
112
+ },
113
+ });
114
+
115
+ if (veScaBindTableResponse.data?.content?.dataType !== 'moveObject')
116
+ return false;
117
+ const veScaBindTableFields = veScaBindTableResponse.data.content
118
+ .fields as any;
119
+ // get obligationId pair
120
+ const obligationId = veScaBindTableFields.value.fields.id as string;
121
+
122
+ return obligationId;
123
+ };
65
124
  /**
66
125
  * Generate borrow incentive normal methods.
67
126
  *
@@ -72,59 +131,89 @@ const requireObligationInfo = async (
72
131
  const generateBorrowIncentiveNormalMethod: GenerateBorrowIncentiveNormalMethod =
73
132
  ({ builder, txBlock }) => {
74
133
  const borrowIncentiveIds: BorrowIncentiveIds = {
75
- borrowIncentivePkg: builder.address.get('borrowIncentive.id'),
134
+ borrowIncentivePkg: IS_VE_SCA_TEST
135
+ ? '0x4d5a7cefa4147b4ace0ca845b20437d6ac0d32e5f2f855171f745472c2576246'
136
+ : builder.address.get('borrowIncentive.id'),
76
137
  query: builder.address.get('borrowIncentive.query'),
138
+ config: builder.address.get('borrowIncentive.config'),
77
139
  incentivePools: builder.address.get('borrowIncentive.incentivePools'),
78
140
  incentiveAccounts: builder.address.get(
79
141
  'borrowIncentive.incentiveAccounts'
80
142
  ),
81
143
  obligationAccessStore: builder.address.get('core.obligationAccessStore'),
82
144
  };
145
+
146
+ const veScaIds: Omit<VescaIds, 'pkgId'> = {
147
+ table: builder.address.get('vesca.table'),
148
+ treasury: builder.address.get('vesca.treasury'),
149
+ config: builder.address.get('vesca.config'),
150
+ };
151
+
83
152
  return {
84
- stakeObligation: (obligationId, obligaionKey) => {
85
- // NOTE: Pools without incentives also need to stake after change obligation,
86
- // the default here use sui as reward coin.
87
- const rewardCoinName = 'sui';
88
- const rewardType = builder.utils.parseCoinType(rewardCoinName);
153
+ stakeObligation: (obligationId, obligationKey) => {
89
154
  txBlock.moveCall(
90
155
  `${borrowIncentiveIds.borrowIncentivePkg}::user::stake`,
91
156
  [
157
+ borrowIncentiveIds.config,
158
+ borrowIncentiveIds.incentivePools,
159
+ borrowIncentiveIds.incentiveAccounts,
160
+ obligationKey,
161
+ obligationId,
162
+ borrowIncentiveIds.obligationAccessStore,
163
+ SUI_CLOCK_OBJECT_ID,
164
+ ]
165
+ );
166
+ },
167
+ stakeObligationWithVesca: (obligationId, obligationKey, veScaKey) => {
168
+ txBlock.moveCall(
169
+ `${borrowIncentiveIds.borrowIncentivePkg}::user::stake_with_ve_sca`,
170
+ [
171
+ borrowIncentiveIds.config,
92
172
  borrowIncentiveIds.incentivePools,
93
173
  borrowIncentiveIds.incentiveAccounts,
94
- obligaionKey,
174
+ obligationKey,
95
175
  obligationId,
96
176
  borrowIncentiveIds.obligationAccessStore,
177
+ veScaIds.config,
178
+ veScaIds.treasury,
179
+ veScaIds.table,
180
+ veScaKey,
97
181
  SUI_CLOCK_OBJECT_ID,
98
182
  ],
99
- [rewardType]
183
+ []
100
184
  );
101
185
  },
102
- unstakeObligation: (obligationId, obligaionKey) => {
103
- // NOTE: Pools without incentives also need to unstake to change obligation,
104
- // the default here use sui as reward coin.
105
- const rewardCoinName = 'sui';
106
- const rewardType = builder.utils.parseCoinType(rewardCoinName);
186
+ unstakeObligation: (obligationId, obligationKey) => {
107
187
  txBlock.moveCall(
108
188
  `${borrowIncentiveIds.borrowIncentivePkg}::user::unstake`,
109
189
  [
190
+ borrowIncentiveIds.config,
110
191
  borrowIncentiveIds.incentivePools,
111
192
  borrowIncentiveIds.incentiveAccounts,
112
- obligaionKey,
193
+ obligationKey,
113
194
  obligationId,
114
195
  SUI_CLOCK_OBJECT_ID,
115
- ],
116
- [rewardType]
196
+ ]
117
197
  );
118
198
  },
119
- claimBorrowIncentive: (obligationId, obligaionKey, coinName) => {
120
- const rewardCoinName = borrowIncentiveRewardCoins[coinName];
199
+ claimBorrowIncentive: (
200
+ obligationId,
201
+ obligationKey,
202
+ coinName,
203
+ rewardCoinName
204
+ ) => {
205
+ const rewardCoinNames = borrowIncentiveRewardCoins[coinName];
206
+ if (rewardCoinNames.includes(rewardCoinName) === false) {
207
+ throw new Error(`Invalid reward coin name ${rewardCoinName}`);
208
+ }
121
209
  const rewardType = builder.utils.parseCoinType(rewardCoinName);
122
210
  return txBlock.moveCall(
123
211
  `${borrowIncentiveIds.borrowIncentivePkg}::user::redeem_rewards`,
124
212
  [
213
+ borrowIncentiveIds.config,
125
214
  borrowIncentiveIds.incentivePools,
126
215
  borrowIncentiveIds.incentiveAccounts,
127
- obligaionKey,
216
+ obligationKey,
128
217
  obligationId,
129
218
  SUI_CLOCK_OBJECT_ID,
130
219
  ],
@@ -144,7 +233,7 @@ const generateBorrowIncentiveNormalMethod: GenerateBorrowIncentiveNormalMethod =
144
233
  *
145
234
  * @param builder - Scallop builder instance.
146
235
  * @param txBlock - TxBlock created by SuiKit .
147
- * @return Spool quick methods.
236
+ * @return Borrow Incentive quick methods.
148
237
  */
149
238
  const generateBorrowIncentiveQuickMethod: GenerateBorrowIncentiveQuickMethod =
150
239
  ({ builder, txBlock }) => {
@@ -165,14 +254,72 @@ const generateBorrowIncentiveQuickMethod: GenerateBorrowIncentiveQuickMethod =
165
254
  !!txBlock.txBlock.blockData.transactions.find(
166
255
  (txn) =>
167
256
  txn.kind === 'MoveCall' &&
168
- txn.target ===
169
- `${builder.address.get('borrowIncentive.id')}::user::unstake`
257
+ (txn.target ===
258
+ `${OLD_BORROW_INCENTIVE_PROTOCOL_ID}::user::unstake` ||
259
+ txn.target ===
260
+ (IS_VE_SCA_TEST
261
+ ? `${'0x4d5a7cefa4147b4ace0ca845b20437d6ac0d32e5f2f855171f745472c2576246'}::user::unstake`
262
+ : `${builder.address.get(
263
+ 'borrowIncentive.id'
264
+ )}::user::unstake`))
170
265
  );
171
266
 
172
267
  if (!obligationLocked || unstakeObligationBeforeStake) {
173
268
  txBlock.stakeObligation(obligationArg, obligationtKeyArg);
174
269
  }
175
270
  },
271
+ stakeObligationWithVeScaQuick: async (
272
+ obligation,
273
+ obligationKey,
274
+ veScaKey
275
+ ) => {
276
+ const {
277
+ obligationId: obligationArg,
278
+ obligationKey: obligationtKeyArg,
279
+ obligationLocked: obligationLocked,
280
+ } = await requireObligationInfo(
281
+ builder,
282
+ txBlock,
283
+ obligation,
284
+ obligationKey
285
+ );
286
+
287
+ const unstakeObligationBeforeStake =
288
+ !!txBlock.txBlock.blockData.transactions.find(
289
+ (txn) =>
290
+ txn.kind === 'MoveCall' &&
291
+ (txn.target ===
292
+ `${OLD_BORROW_INCENTIVE_PROTOCOL_ID}::user::unstake` ||
293
+ txn.target ===
294
+ (IS_VE_SCA_TEST
295
+ ? `${'0x4d5a7cefa4147b4ace0ca845b20437d6ac0d32e5f2f855171f745472c2576246'}::user::unstake`
296
+ : `${builder.address.get(
297
+ 'borrowIncentive.id'
298
+ )}::user::unstake`))
299
+ );
300
+
301
+ if (!obligationLocked || unstakeObligationBeforeStake) {
302
+ const veSca = await requireVeSca(builder, txBlock, veScaKey);
303
+ if (veSca) {
304
+ const bindedObligationId = await getBindedObligationId(
305
+ builder,
306
+ veSca.keyId
307
+ );
308
+ // if bindedObligationId is equal to obligationId, then use it again
309
+ if (!bindedObligationId || bindedObligationId === obligationArg) {
310
+ txBlock.stakeObligationWithVesca(
311
+ obligationArg,
312
+ obligationtKeyArg,
313
+ veSca.keyId
314
+ );
315
+ } else {
316
+ txBlock.stakeObligation(obligationArg, obligationtKeyArg);
317
+ }
318
+ } else {
319
+ txBlock.stakeObligation(obligationArg, obligationtKeyArg);
320
+ }
321
+ }
322
+ },
176
323
  unstakeObligationQuick: async (obligation, obligationKey) => {
177
324
  const {
178
325
  obligationId: obligationArg,
@@ -191,6 +338,7 @@ const generateBorrowIncentiveQuickMethod: GenerateBorrowIncentiveQuickMethod =
191
338
  },
192
339
  claimBorrowIncentiveQuick: async (
193
340
  coinName,
341
+ rewardCoinName,
194
342
  obligation,
195
343
  obligationKey
196
344
  ) => {
@@ -207,7 +355,8 @@ const generateBorrowIncentiveQuickMethod: GenerateBorrowIncentiveQuickMethod =
207
355
  return txBlock.claimBorrowIncentive(
208
356
  obligationArg,
209
357
  obligationtKeyArg,
210
- coinName
358
+ coinName,
359
+ rewardCoinName
211
360
  );
212
361
  },
213
362
  };
@@ -3,6 +3,7 @@ import { SuiTxBlock as SuiKitTxBlock } from '@scallop-io/sui-kit';
3
3
  import { newCoreTxBlock } from './coreBuilder';
4
4
  import { newSpoolTxBlock } from './spoolBuilder';
5
5
  import { newBorrowIncentiveTxBlock } from './borrowIncentiveBuilder';
6
+ import { newVeScaTxBlock } from './vescaBuilder';
6
7
  import type { ScallopBuilder } from '../models';
7
8
  import type { ScallopTxBlock } from '../types';
8
9
 
@@ -17,16 +18,19 @@ export const newScallopTxBlock = (
17
18
  builder: ScallopBuilder,
18
19
  initTxBlock?: ScallopTxBlock | SuiKitTxBlock | TransactionBlock
19
20
  ): ScallopTxBlock => {
21
+ const vescaTxBlock = newVeScaTxBlock(builder, initTxBlock);
20
22
  const borrowIncentiveTxBlock = newBorrowIncentiveTxBlock(
21
23
  builder,
22
- initTxBlock
24
+ vescaTxBlock
23
25
  );
24
26
  const spoolTxBlock = newSpoolTxBlock(builder, borrowIncentiveTxBlock);
25
27
  const coreTxBlock = newCoreTxBlock(builder, spoolTxBlock);
26
28
 
27
29
  return new Proxy(coreTxBlock, {
28
30
  get: (target, prop) => {
29
- if (prop in borrowIncentiveTxBlock) {
31
+ if (prop in vescaTxBlock) {
32
+ return Reflect.get(vescaTxBlock, prop);
33
+ } else if (prop in borrowIncentiveTxBlock) {
30
34
  return Reflect.get(borrowIncentiveTxBlock, prop);
31
35
  } else if (prop in spoolTxBlock) {
32
36
  return Reflect.get(spoolTxBlock, prop);
@@ -0,0 +1,392 @@
1
+ import {
2
+ SUI_CLOCK_OBJECT_ID,
3
+ SuiAddressArg,
4
+ SuiTxBlock,
5
+ TransactionBlock,
6
+ SuiTxBlock as SuiKitTxBlock,
7
+ } from '@scallop-io/sui-kit';
8
+ import { SCA_COIN_TYPE } from 'src/constants';
9
+ import { ScallopBuilder } from '../models';
10
+ import { getVeSca, getVeScas } from '../queries';
11
+ import {
12
+ requireSender,
13
+ checkLockSca,
14
+ checkExtendLockPeriod,
15
+ checkExtendLockAmount,
16
+ checkRenewExpiredVeSca,
17
+ checkVesca,
18
+ } from '../utils';
19
+ import type {
20
+ TransactionObjectArgument,
21
+ SuiObjectArg,
22
+ } from '@scallop-io/sui-kit';
23
+ import type {
24
+ GenerateVeScaNormalMethod,
25
+ GenerateVeScaQuickMethod,
26
+ ScallopTxBlock,
27
+ SuiTxBlockWithVeScaNormalMethods,
28
+ VeScaTxBlock,
29
+ VescaIds,
30
+ } from 'src/types';
31
+
32
+ /**
33
+ * Check and get veSCA data from transaction block.
34
+ *
35
+ * @description
36
+ * If the veScaKey id is provided, directly return it.
37
+ * Otherwise, automatically get veScaKey from the sender.
38
+ *
39
+ * @param builder - Scallop builder instance.
40
+ * @param txBlock - TxBlock created by SuiKit.
41
+ * @param veScaKey - veSCA key.
42
+ * @return veSCA key, ID, locked amount and unlock at timestamp.
43
+ */
44
+
45
+ export const requireVeSca = async (
46
+ ...params: [
47
+ builder: ScallopBuilder,
48
+ SuiTxBlock: SuiTxBlock,
49
+ veScaKey?: SuiAddressArg,
50
+ ]
51
+ ) => {
52
+ const [builder, txBlock, veScaKey] = params;
53
+ if (params.length === 3 && veScaKey && typeof veScaKey === 'string') {
54
+ const veSca = await getVeSca(builder.query, veScaKey);
55
+
56
+ if (!veSca) {
57
+ return undefined;
58
+ }
59
+
60
+ return veSca;
61
+ }
62
+
63
+ const sender = requireSender(txBlock);
64
+ const veScas = await getVeScas(builder.query, sender);
65
+ if (veScas.length === 0) {
66
+ return undefined;
67
+ }
68
+
69
+ return veScas[0];
70
+ };
71
+
72
+ /**
73
+ * Generate veSCA normal methods.
74
+ *
75
+ * @param builder - Scallop builder instance.
76
+ * @param txBlock - TxBlock created by SuiKit .
77
+ * @return veSCA normal methods.
78
+ */
79
+ const generateNormalVeScaMethod: GenerateVeScaNormalMethod = ({
80
+ builder,
81
+ txBlock,
82
+ }) => {
83
+ const veScaIds: VescaIds = {
84
+ pkgId: builder.address.get('vesca.id'),
85
+ table: builder.address.get('vesca.table'),
86
+ treasury: builder.address.get('vesca.treasury'),
87
+ config: builder.address.get('vesca.config'),
88
+ };
89
+
90
+ return {
91
+ lockSca: (scaCoin, unlockAtInSecondTimestamp) => {
92
+ return txBlock.moveCall(
93
+ `${veScaIds.pkgId}::ve_sca::mint_ve_sca_key`,
94
+ [
95
+ veScaIds.config,
96
+ veScaIds.table,
97
+ veScaIds.treasury,
98
+ scaCoin,
99
+ unlockAtInSecondTimestamp,
100
+ SUI_CLOCK_OBJECT_ID,
101
+ ],
102
+ []
103
+ );
104
+ },
105
+ extendLockPeriod: (veScaKey, newUnlockAtInSecondTimestamp) => {
106
+ txBlock.moveCall(
107
+ `${veScaIds.pkgId}::ve_sca::extend_lock_period`,
108
+ [
109
+ veScaIds.config,
110
+ veScaKey,
111
+ veScaIds.table,
112
+ veScaIds.treasury,
113
+ newUnlockAtInSecondTimestamp,
114
+ SUI_CLOCK_OBJECT_ID,
115
+ ],
116
+ []
117
+ );
118
+ },
119
+ extendLockAmount: (veScaKey, scaCoin) => {
120
+ txBlock.moveCall(
121
+ `${veScaIds.pkgId}::ve_sca::lock_more_sca`,
122
+ [
123
+ veScaIds.config,
124
+ veScaKey,
125
+ veScaIds.table,
126
+ veScaIds.treasury,
127
+ scaCoin,
128
+ SUI_CLOCK_OBJECT_ID,
129
+ ],
130
+ []
131
+ );
132
+ },
133
+ renewExpiredVeSca: (veScaKey, scaCoin, newUnlockAtInSecondTimestamp) => {
134
+ txBlock.moveCall(
135
+ `${veScaIds.pkgId}::ve_sca::renew_expired_ve_sca`,
136
+ [
137
+ veScaIds.config,
138
+ veScaKey,
139
+ veScaIds.table,
140
+ veScaIds.treasury,
141
+ scaCoin,
142
+ newUnlockAtInSecondTimestamp,
143
+ SUI_CLOCK_OBJECT_ID,
144
+ ],
145
+ []
146
+ );
147
+ },
148
+ redeemSca: (veScaKey) => {
149
+ return txBlock.moveCall(
150
+ `${veScaIds.pkgId}::ve_sca::redeem`,
151
+ [
152
+ veScaIds.config,
153
+ veScaKey,
154
+ veScaIds.table,
155
+ veScaIds.treasury,
156
+ SUI_CLOCK_OBJECT_ID,
157
+ ],
158
+ []
159
+ );
160
+ },
161
+ };
162
+ };
163
+
164
+ /**
165
+ * Generate veSCA quick methods.
166
+ *
167
+ * @description
168
+ * The quick methods are the same as the normal methods, but they will automatically
169
+ * help users organize transaction blocks, include get veSca info, and transfer
170
+ * coins to the sender. So, they are all asynchronous methods.
171
+ *
172
+ * @param builder - Scallop builder instance.
173
+ * @param txBlock - TxBlock created by SuiKit .
174
+ * @return veSCA quick methods.
175
+ */
176
+ const generateQuickVeScaMethod: GenerateVeScaQuickMethod = ({
177
+ builder,
178
+ txBlock,
179
+ }) => {
180
+ return {
181
+ lockScaQuick: async (amountOrCoin, lockPeriodInDays, autoCheck = true) => {
182
+ const sender = requireSender(txBlock);
183
+ const veSca = await requireVeSca(builder, txBlock);
184
+
185
+ let scaCoin: TransactionObjectArgument | SuiObjectArg | undefined =
186
+ undefined;
187
+ const transferObjects = [];
188
+ if (amountOrCoin !== undefined && typeof amountOrCoin === 'number') {
189
+ const coins = await builder.utils.selectCoinIds(
190
+ amountOrCoin,
191
+ SCA_COIN_TYPE,
192
+ sender
193
+ );
194
+ const [takeCoin, leftCoin] = txBlock.takeAmountFromCoins(
195
+ coins,
196
+ amountOrCoin
197
+ );
198
+ scaCoin = takeCoin;
199
+ transferObjects.push(leftCoin);
200
+ } else {
201
+ // With amountOrCoin is SuiObjectArg, we cannot validate the minimum sca amount for locking and topup
202
+ scaCoin = amountOrCoin;
203
+ }
204
+
205
+ const newUnlockAt = builder.utils.getUnlockAt(
206
+ lockPeriodInDays,
207
+ veSca?.unlockAt
208
+ );
209
+ if (autoCheck)
210
+ checkLockSca(
211
+ amountOrCoin,
212
+ lockPeriodInDays,
213
+ newUnlockAt,
214
+ veSca?.unlockAt
215
+ );
216
+ console.log(
217
+ new Date(newUnlockAt * 1000).toLocaleString('en-CA', {
218
+ hour12: true,
219
+ })
220
+ );
221
+
222
+ const isInitialLock = !veSca?.unlockAt;
223
+ const isLockExpired =
224
+ !isInitialLock && veSca.unlockAt * 1000 <= new Date().getTime();
225
+ if (isInitialLock || isLockExpired) {
226
+ if (scaCoin) {
227
+ if (isInitialLock) {
228
+ const veScaKey = txBlock.lockSca(scaCoin, newUnlockAt);
229
+ transferObjects.push(veScaKey);
230
+ } else {
231
+ // user must withdraw current unlocked SCA first if any
232
+ if (veSca.lockedScaAmount !== 0) {
233
+ const unlockedSca = txBlock.redeemSca(veSca.keyId);
234
+ transferObjects.push(unlockedSca);
235
+ }
236
+ // enforce renew on expired
237
+ txBlock.renewExpiredVeSca(veSca.keyId, scaCoin, newUnlockAt);
238
+ }
239
+ }
240
+ } else {
241
+ if (!!scaCoin && !!lockPeriodInDays) {
242
+ txBlock.extendLockPeriod(veSca.keyId, newUnlockAt);
243
+ txBlock.extendLockAmount(veSca.keyId, scaCoin);
244
+ } else if (lockPeriodInDays) {
245
+ txBlock.extendLockPeriod(veSca.keyId, newUnlockAt);
246
+ } else if (scaCoin) {
247
+ txBlock.extendLockAmount(veSca.keyId, scaCoin);
248
+ }
249
+ }
250
+
251
+ if (transferObjects.length > 0) {
252
+ txBlock.transferObjects(transferObjects, sender);
253
+ }
254
+ },
255
+ extendLockPeriodQuick: async (
256
+ lockPeriodInDays: number,
257
+ veScaKey?: SuiAddressArg,
258
+ autoCheck = true
259
+ ) => {
260
+ const veSca = await requireVeSca(builder, txBlock, veScaKey);
261
+
262
+ const newUnlockAt = builder.utils.getUnlockAt(lockPeriodInDays);
263
+ if (autoCheck)
264
+ checkExtendLockPeriod(lockPeriodInDays, newUnlockAt, veSca?.unlockAt);
265
+
266
+ if (veSca) {
267
+ txBlock.extendLockPeriod(veSca.keyId, newUnlockAt);
268
+ }
269
+ },
270
+ extendLockAmountQuick: async (
271
+ scaAmount: number,
272
+ veScaKey?: SuiAddressArg,
273
+ autoCheck = true
274
+ ) => {
275
+ const sender = requireSender(txBlock);
276
+ const veSca = await requireVeSca(builder, txBlock, veScaKey);
277
+
278
+ if (autoCheck) checkExtendLockAmount(scaAmount, veSca?.unlockAt);
279
+
280
+ if (veSca) {
281
+ const scaCoins = await builder.utils.selectCoinIds(
282
+ scaAmount,
283
+ SCA_COIN_TYPE,
284
+ sender
285
+ );
286
+ const [takeCoin, leftCoin] = txBlock.takeAmountFromCoins(
287
+ scaCoins,
288
+ scaAmount
289
+ );
290
+
291
+ txBlock.extendLockAmount(veSca.keyId, takeCoin);
292
+ txBlock.transferObjects([leftCoin], sender);
293
+ }
294
+ },
295
+ renewExpiredVeScaQuick: async (
296
+ scaAmount: number,
297
+ lockPeriodInDays: number,
298
+ veScaKey?: SuiAddressArg,
299
+ autoCheck = true
300
+ ) => {
301
+ const sender = requireSender(txBlock);
302
+ const veSca = await requireVeSca(builder, txBlock, veScaKey);
303
+
304
+ const newUnlockAt = builder.utils.getUnlockAt(
305
+ lockPeriodInDays,
306
+ veSca?.unlockAt
307
+ );
308
+ if (autoCheck)
309
+ checkRenewExpiredVeSca(scaAmount, lockPeriodInDays, veSca?.unlockAt);
310
+
311
+ if (veSca) {
312
+ const transferObjects = [];
313
+ if (veSca.lockedScaAmount !== 0) {
314
+ const unlockedSca = txBlock.redeemSca(veSca.keyId);
315
+ transferObjects.push(unlockedSca);
316
+ }
317
+ const scaCoins = await builder.utils.selectCoinIds(
318
+ scaAmount,
319
+ SCA_COIN_TYPE,
320
+ sender
321
+ );
322
+ const [takeCoin, leftCoin] = txBlock.takeAmountFromCoins(
323
+ scaCoins,
324
+ scaAmount
325
+ );
326
+ transferObjects.push(leftCoin);
327
+
328
+ txBlock.renewExpiredVeSca(veSca.keyId, takeCoin, newUnlockAt);
329
+ txBlock.transferObjects(transferObjects, sender);
330
+ }
331
+ },
332
+ redeemScaQuick: async (veScaKey?: SuiAddressArg) => {
333
+ const sender = requireSender(txBlock);
334
+ const veSca = await requireVeSca(builder, txBlock, veScaKey);
335
+
336
+ checkVesca(veSca?.unlockAt);
337
+
338
+ if (veSca) {
339
+ const sca = txBlock.redeemSca(veSca.keyId);
340
+ txBlock.transferObjects([sca], sender);
341
+ }
342
+ },
343
+ };
344
+ };
345
+
346
+ /**
347
+ * Create an enhanced transaction block instance for interaction with veSCA modules of the Scallop contract.
348
+ *
349
+ * @param builder - Scallop builder instance.
350
+ * @param initTxBlock - Scallop txBlock, txBlock created by SuiKit, or original transaction block.
351
+ * @return Scallop borrow incentive txBlock.
352
+ */
353
+ export const newVeScaTxBlock = (
354
+ builder: ScallopBuilder,
355
+ initTxBlock?: ScallopTxBlock | SuiKitTxBlock | TransactionBlock
356
+ ) => {
357
+ const txBlock =
358
+ initTxBlock instanceof TransactionBlock
359
+ ? new SuiKitTxBlock(initTxBlock)
360
+ : initTxBlock
361
+ ? initTxBlock
362
+ : new SuiKitTxBlock();
363
+
364
+ const normalMethod = generateNormalVeScaMethod({
365
+ builder,
366
+ txBlock,
367
+ });
368
+
369
+ const normalTxBlock = new Proxy(txBlock, {
370
+ get: (target, prop) => {
371
+ if (prop in normalMethod) {
372
+ return Reflect.get(normalMethod, prop);
373
+ }
374
+ return Reflect.get(target, prop);
375
+ },
376
+ }) as SuiTxBlockWithVeScaNormalMethods;
377
+
378
+ // TODO: Add quickMethod for veSCA
379
+ const quickMethod = generateQuickVeScaMethod({
380
+ builder,
381
+ txBlock: normalTxBlock,
382
+ });
383
+
384
+ return new Proxy(normalTxBlock, {
385
+ get: (target, prop) => {
386
+ if (prop in quickMethod) {
387
+ return Reflect.get(quickMethod, prop);
388
+ }
389
+ return Reflect.get(target, prop);
390
+ },
391
+ }) as VeScaTxBlock;
392
+ };