@scallop-io/sui-scallop-sdk 0.46.36 → 0.46.37

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 (41) hide show
  1. package/dist/builders/loyaltyProgramBuilder.d.ts +12 -0
  2. package/dist/builders/referralBuilder.d.ts +1 -1
  3. package/dist/constants/testAddress.d.ts +2 -0
  4. package/dist/index.js +567 -71
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +557 -61
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/models/scallopQuery.d.ts +10 -3
  9. package/dist/models/scallopUtils.d.ts +1 -1
  10. package/dist/queries/index.d.ts +1 -0
  11. package/dist/queries/loyaltyProgramQuery.d.ts +10 -0
  12. package/dist/queries/vescaQuery.d.ts +8 -6
  13. package/dist/types/address.d.ts +6 -0
  14. package/dist/types/builder/index.d.ts +3 -1
  15. package/dist/types/builder/loyaltyProgram.d.ts +23 -0
  16. package/dist/types/builder/vesca.d.ts +16 -0
  17. package/dist/types/query/index.d.ts +1 -0
  18. package/dist/types/query/loyaltyProgram.d.ts +5 -0
  19. package/dist/types/query/vesca.d.ts +18 -0
  20. package/dist/utils/builder.d.ts +6 -5
  21. package/package.json +7 -6
  22. package/src/builders/index.ts +6 -1
  23. package/src/builders/loyaltyProgramBuilder.ts +115 -0
  24. package/src/builders/referralBuilder.ts +1 -1
  25. package/src/builders/vescaBuilder.ts +5 -1
  26. package/src/constants/testAddress.ts +383 -0
  27. package/src/models/scallopAddress.ts +12 -2
  28. package/src/models/scallopCache.ts +0 -1
  29. package/src/models/scallopQuery.ts +16 -5
  30. package/src/models/scallopUtils.ts +3 -3
  31. package/src/queries/index.ts +1 -0
  32. package/src/queries/loyaltyProgramQuery.ts +77 -0
  33. package/src/queries/vescaQuery.ts +70 -15
  34. package/src/types/address.ts +6 -0
  35. package/src/types/builder/index.ts +3 -0
  36. package/src/types/builder/loyaltyProgram.ts +35 -0
  37. package/src/types/builder/vesca.ts +16 -0
  38. package/src/types/query/index.ts +1 -0
  39. package/src/types/query/loyaltyProgram.ts +5 -0
  40. package/src/types/query/vesca.ts +21 -0
  41. package/src/utils/builder.ts +69 -53
@@ -1,5 +1,5 @@
1
1
  import BigNumber from 'bignumber.js';
2
- import { Vesca } from '../types';
2
+ import { VeScaTreasuryFields, VeScaTreasuryInfo, Vesca } from '../types';
3
3
  import {
4
4
  type SuiObjectResponse,
5
5
  type SuiObjectData,
@@ -9,6 +9,7 @@ import type { ScallopQuery } from '../models';
9
9
  import { MAX_LOCK_DURATION } from 'src/constants';
10
10
  import { SUI_CLOCK_OBJECT_ID, SuiTxBlock } from '@scallop-io/sui-kit';
11
11
  import { bcs } from '@mysten/sui.js/bcs';
12
+ import { z as zod } from 'zod';
12
13
  /**
13
14
  * Query all owned veSca key.
14
15
  *
@@ -63,11 +64,10 @@ export const getVescaKeys = async (
63
64
  */
64
65
  export const getVeScas = async (query: ScallopQuery, ownerAddress?: string) => {
65
66
  const keyObjectDatas = await getVescaKeys(query, ownerAddress);
66
- const keyObjectId: string[] = keyObjectDatas.map((data) => data.objectId);
67
67
 
68
- const veScas: (Vesca | undefined)[] = Array(keyObjectId.length).fill(null);
69
- const tasks = keyObjectId.map(async (keyId, idx) => {
70
- const veSca = await getVeSca(query, keyId);
68
+ const veScas: Vesca[] = Array(keyObjectDatas.length).fill(null);
69
+ const tasks = keyObjectDatas.map(async (veScaKey, idx) => {
70
+ const veSca = await getVeSca(query, veScaKey);
71
71
  if (veSca) {
72
72
  veScas[idx] = veSca;
73
73
  }
@@ -79,22 +79,33 @@ export const getVeScas = async (query: ScallopQuery, ownerAddress?: string) => {
79
79
  .sort((a, b) => b!.currentVeScaBalance - a!.currentVeScaBalance);
80
80
  };
81
81
 
82
+ const SuiObjectRefZod = zod.object({
83
+ objectId: zod.string(),
84
+ digest: zod.string(),
85
+ version: zod.string(),
86
+ });
87
+
88
+ type SuiObjectRefType = zod.infer<typeof SuiObjectRefZod>;
82
89
  /**
83
90
  * Get veSca data.
84
91
  *
85
92
  * @param query - The Scallop query instance.
86
- * @param veScaKeyId - The vesca key id.
93
+ * @param veScaKey - The vesca key id.
87
94
  * @param ownerAddress - The owner address.
88
95
  * @returns Vesca data.
89
96
  */
90
97
  export const getVeSca = async (
91
98
  query: ScallopQuery,
92
- veScaKeyId?: string,
99
+ veScaKey?: string | SuiObjectData,
93
100
  ownerAddress?: string
94
101
  ) => {
95
102
  const tableId = query.address.get(`vesca.tableId`);
96
- veScaKeyId =
97
- veScaKeyId || (await getVescaKeys(query, ownerAddress))[0].objectId;
103
+ veScaKey = veScaKey || (await getVescaKeys(query, ownerAddress))[0];
104
+
105
+ if (!veScaKey) return undefined;
106
+ if (typeof veScaKey === 'object') {
107
+ veScaKey = SuiObjectRefZod.parse(veScaKey) as SuiObjectRefType;
108
+ }
98
109
 
99
110
  let vesca: Vesca | undefined = undefined;
100
111
 
@@ -103,7 +114,7 @@ export const getVeSca = async (
103
114
  parentId: tableId,
104
115
  name: {
105
116
  type: '0x2::object::ID',
106
- value: veScaKeyId,
117
+ value: typeof veScaKey === 'string' ? veScaKey : veScaKey.objectId,
107
118
  },
108
119
  });
109
120
  const veScaDynamicFieldObject = veScaDynamicFieldObjectResponse.data;
@@ -131,7 +142,9 @@ export const getVeSca = async (
131
142
 
132
143
  vesca = {
133
144
  id: veScaDynamicFieldObject.objectId,
134
- keyId: veScaKeyId,
145
+ keyId: typeof veScaKey === 'string' ? veScaKey : veScaKey.objectId,
146
+ keyObject: typeof veScaKey === 'string' ? undefined : veScaKey,
147
+ object: SuiObjectRefZod.parse(veScaDynamicFieldObjectResponse.data),
135
148
  lockedScaAmount,
136
149
  lockedScaCoin,
137
150
  currentVeScaBalance,
@@ -145,12 +158,13 @@ export const getVeSca = async (
145
158
  /**
146
159
  * Get current total veSca treasury amount.
147
160
  */
148
- export const getTotalVeScaTreasuryAmount = async (
149
- query: ScallopQuery
161
+ const getTotalVeScaTreasuryAmount = async (
162
+ query: ScallopQuery,
163
+ veScaTreasury: SuiObjectData
150
164
  ): Promise<string> => {
151
165
  const veScaPkgId = query.address.get('vesca.id');
152
166
  const veScaConfig = query.address.get('vesca.config');
153
- const veScaTreasury = query.address.get('vesca.treasury');
167
+ veScaTreasury = veScaTreasury ?? query.address.get('vesca.treasury');
154
168
 
155
169
  // refresh query
156
170
  const refreshQueryTarget = `${veScaPkgId}::treasury::refresh`;
@@ -201,7 +215,6 @@ export const getTotalVeScaTreasuryAmount = async (
201
215
  queryFn: async () => {
202
216
  return await query.suiKit.inspectTxn(txBytes);
203
217
  },
204
- staleTime: 8000,
205
218
  });
206
219
 
207
220
  const results = res.results;
@@ -213,3 +226,45 @@ export const getTotalVeScaTreasuryAmount = async (
213
226
 
214
227
  return '0';
215
228
  };
229
+
230
+ /**
231
+ * Get veSCA treasury informations
232
+ * @param query
233
+ * @returns VeScaTreasuryInfo
234
+ */
235
+ export const getVeScaTreasuryInfo = async (
236
+ query: ScallopQuery
237
+ ): Promise<VeScaTreasuryInfo | null> => {
238
+ const veScaTreasuryId = query.address.get('vesca.treasury');
239
+ const veScaTreasury = await query.cache.queryGetObject(veScaTreasuryId, {
240
+ showContent: true,
241
+ });
242
+
243
+ if (!veScaTreasury || veScaTreasury.data?.content?.dataType !== 'moveObject')
244
+ return null;
245
+
246
+ const treasuryFields = veScaTreasury.data.content
247
+ .fields as VeScaTreasuryFields;
248
+
249
+ console.log(treasuryFields);
250
+ const totalLockedSca = BigNumber(
251
+ treasuryFields.unlock_schedule.fields.locked_sca_amount
252
+ )
253
+ .shiftedBy(-9)
254
+ .toNumber();
255
+ const totalVeSca = BigNumber(
256
+ (await getTotalVeScaTreasuryAmount(query, veScaTreasury.data)) ?? 0
257
+ )
258
+ .shiftedBy(-9)
259
+ .toNumber();
260
+ const averageLockingPeriod =
261
+ totalLockedSca > 0 ? (totalVeSca / totalLockedSca) * 4 : 0; // in years
262
+
263
+ const averageLockingPeriodUnit = 'year';
264
+ return {
265
+ totalLockedSca,
266
+ totalVeSca,
267
+ averageLockingPeriod,
268
+ averageLockingPeriodUnit,
269
+ };
270
+ };
@@ -115,6 +115,12 @@ export interface AddressesInterface {
115
115
  tiersTableId: string;
116
116
  authorizedWitnessList: string;
117
117
  };
118
+ loyaltyProgram: {
119
+ id: string;
120
+ object: string;
121
+ rewardPool: string;
122
+ userRewardTableId: string;
123
+ };
118
124
  }
119
125
 
120
126
  type AddressPathsProps<T> = T extends string
@@ -3,14 +3,17 @@ import type { SpoolTxBlock } from './spool';
3
3
  import type { BorrowIncentiveTxBlock } from './borrowIncentive';
4
4
  import type { VeScaTxBlock } from './vesca';
5
5
  import type { ReferralTxBlock } from './referral';
6
+ import { LoyaltyProgramTxBlock } from './loyaltyProgram';
6
7
 
7
8
  export type * from './core';
8
9
  export type * from './spool';
9
10
  export type * from './borrowIncentive';
10
11
  export type * from './vesca';
12
+ export type * from './loyaltyProgram';
11
13
 
12
14
  export type ScallopTxBlock = CoreTxBlock &
13
15
  SpoolTxBlock &
14
16
  ReferralTxBlock &
17
+ LoyaltyProgramTxBlock &
15
18
  BorrowIncentiveTxBlock &
16
19
  VeScaTxBlock;
@@ -0,0 +1,35 @@
1
+ import {
2
+ SuiObjectArg,
3
+ SuiTxBlock as SuiKitTxBlock,
4
+ TransactionResult,
5
+ } from '@scallop-io/sui-kit';
6
+ import { type ScallopBuilder } from 'src/models';
7
+
8
+ export type LoyaltyProgramIds = {
9
+ loyaltyProgramPkgId: string;
10
+ rewardPool: string;
11
+ userRewardTableId: string;
12
+ };
13
+
14
+ export type LoyaltyProgramNormalMethods = {
15
+ claimLoyaltyRevenue: (veScaKey: SuiObjectArg) => TransactionResult;
16
+ };
17
+
18
+ export type LoyaltyProgramQuickMethods = {
19
+ claimLoyaltyRevenueQuick: (veScaKey?: SuiObjectArg) => Promise<void>;
20
+ };
21
+
22
+ export type SuiTxBlockWithLoyaltyProgramNormalMethods = SuiKitTxBlock &
23
+ LoyaltyProgramNormalMethods;
24
+ export type LoyaltyProgramTxBlock = SuiTxBlockWithLoyaltyProgramNormalMethods &
25
+ LoyaltyProgramQuickMethods;
26
+
27
+ export type GenerateLoyaltyProgramNormalMethod = (params: {
28
+ builder: ScallopBuilder;
29
+ txBlock: SuiKitTxBlock;
30
+ }) => LoyaltyProgramNormalMethods;
31
+
32
+ export type GenerateLoyaltyProgramQuickMethod = (params: {
33
+ builder: ScallopBuilder;
34
+ txBlock: SuiTxBlockWithLoyaltyProgramNormalMethods;
35
+ }) => LoyaltyProgramQuickMethods;
@@ -33,6 +33,22 @@ export type VeScaNormalMethods = {
33
33
  };
34
34
 
35
35
  export type VeScaQuickMethods = {
36
+ /**
37
+ * Quick methods to automate
38
+ * lock initial SCA, extend lock period, lock more SCA, renew expired VeSCA, and redeem SCA
39
+ *
40
+ * **Flow:**
41
+ * - If only `amountOrCoin` is provided, it will lock the amount of existing not expired veSCA
42
+ * - If only `lockPeriodInDays` is provided, it will extend the lock period of existing not expired veSCA
43
+ *
44
+ * **Note:**
45
+ * - If one or both flow above is used on a expired veSCA, it will claim the unlocked SCA
46
+ * and renew the veSCA first, and then flow continues
47
+ * - If users has no veSCA yet, they need to provide both `amountOrCoin` and `lockPeriodInDays` for initial lock
48
+ * @param amountOrCoin
49
+ * @param lockPeriodInDays
50
+ * @param autoCheck
51
+ */
36
52
  lockScaQuick(
37
53
  amountOrCoin?: SuiObjectArg | number,
38
54
  lockPeriodInDays?: number,
@@ -3,3 +3,4 @@ export type * from './spool';
3
3
  export type * from './borrowIncentive';
4
4
  export type * from './portfolio';
5
5
  export type * from './vesca';
6
+ export type * from './loyaltyProgram';
@@ -0,0 +1,5 @@
1
+ export type LoyaltyProgramInfo = {
2
+ pendingReward: number;
3
+ totalPoolReward: number;
4
+ isClaimEnabled: boolean;
5
+ };
@@ -1,8 +1,29 @@
1
+ import type { SuiObjectRef } from '@mysten/sui.js/client';
2
+
1
3
  export type Vesca = {
2
4
  id: string;
3
5
  keyId: string;
6
+ keyObject?: SuiObjectRef;
7
+ object?: SuiObjectRef;
4
8
  lockedScaAmount: string;
5
9
  lockedScaCoin: number;
6
10
  currentVeScaBalance: number;
7
11
  unlockAt: number;
8
12
  };
13
+
14
+ export type VeScaTreasuryFields = {
15
+ total_ve_sca_amount: string;
16
+ sca_balance: string;
17
+ unlock_schedule: {
18
+ fields: {
19
+ locked_sca_amount: string;
20
+ };
21
+ };
22
+ };
23
+
24
+ export type VeScaTreasuryInfo = {
25
+ totalLockedSca: number;
26
+ totalVeSca: number;
27
+ averageLockingPeriod: number;
28
+ averageLockingPeriodUnit: string;
29
+ };
@@ -22,16 +22,61 @@ export const requireSender = (txBlock: SuiKitTxBlock) => {
22
22
  return sender;
23
23
  };
24
24
 
25
+ export const checkVesca = (prevUnlockAtInMillisTimestamp?: number) => {
26
+ if (prevUnlockAtInMillisTimestamp === undefined) {
27
+ throw new Error('veSca not found');
28
+ }
29
+ };
30
+
31
+ export const checkVescaExpired = (prevUnlockAtInMillisTimestamp: number) => {
32
+ if (prevUnlockAtInMillisTimestamp <= new Date().getTime()) {
33
+ throw new Error('veSca is expired, use renewExpiredVeScaQuick instead');
34
+ }
35
+ };
36
+
37
+ export const checkExtendLockPeriod = (
38
+ lockPeriodInDays: number,
39
+ newUnlockAtInSecondTimestamp: number,
40
+ prevUnlockAtInMillisTimestamp?: number
41
+ ) => {
42
+ checkVesca(prevUnlockAtInMillisTimestamp);
43
+ checkVescaExpired(prevUnlockAtInMillisTimestamp!);
44
+ const prevUnlockAtInSecondTimestamp = Math.floor(
45
+ prevUnlockAtInMillisTimestamp! / 1000
46
+ );
47
+ if (lockPeriodInDays < 1) {
48
+ throw new Error('Minimum lock period is 1 day');
49
+ }
50
+
51
+ const availableLockPeriodInDays = Math.floor(
52
+ (newUnlockAtInSecondTimestamp - prevUnlockAtInSecondTimestamp) /
53
+ UNLOCK_ROUND_DURATION
54
+ );
55
+ console.log('availableLockPeriodInDays', availableLockPeriodInDays);
56
+ if (lockPeriodInDays > availableLockPeriodInDays) {
57
+ throw new Error(
58
+ `Cannot extend lock period by ${lockPeriodInDays} days, maximum lock period is ~4 years (${MAX_LOCK_ROUNDS} days), remaining lock period is ${
59
+ MAX_LOCK_ROUNDS - availableLockPeriodInDays
60
+ }`
61
+ );
62
+ }
63
+ };
64
+
25
65
  export const checkLockSca = (
26
- scaAmountOrCoin?: number | SuiObjectArg | undefined,
66
+ scaAmountOrCoin: number | SuiObjectArg | undefined,
27
67
  lockPeriodInDays?: number,
28
68
  newUnlockAtInSecondTimestamp?: number,
29
- prevUnlockAtInSecondTimestamp?: number
69
+ prevUnlockAtInMillisTimestamp?: number
30
70
  ) => {
71
+ const prevUnlockAtInSecondTimestamp = prevUnlockAtInMillisTimestamp
72
+ ? Math.floor(prevUnlockAtInMillisTimestamp / 1000)
73
+ : undefined;
31
74
  const isInitialLock = !prevUnlockAtInSecondTimestamp;
32
75
  const isLockExpired =
33
76
  !isInitialLock &&
34
77
  prevUnlockAtInSecondTimestamp * 1000 <= new Date().getTime();
78
+
79
+ // handle for initial lock / renewing expired veSca
35
80
  if (isInitialLock || isLockExpired) {
36
81
  if (scaAmountOrCoin !== undefined && lockPeriodInDays !== undefined) {
37
82
  if (lockPeriodInDays <= 0) {
@@ -61,7 +106,9 @@ export const checkLockSca = (
61
106
  );
62
107
  }
63
108
  } else {
64
- checkVesca(prevUnlockAtInSecondTimestamp);
109
+ // handle for extending lock period / top up / both
110
+ checkVesca(prevUnlockAtInMillisTimestamp);
111
+ checkVescaExpired(prevUnlockAtInMillisTimestamp!);
65
112
  if (
66
113
  typeof scaAmountOrCoin === 'number' &&
67
114
  scaAmountOrCoin < MIN_TOP_UP_AMOUNT
@@ -69,43 +116,12 @@ export const checkLockSca = (
69
116
  throw new Error('Minimum top up amount is 1 SCA');
70
117
  }
71
118
 
72
- if (!!newUnlockAtInSecondTimestamp && !!prevUnlockAtInSecondTimestamp) {
73
- const totalLockDuration =
74
- newUnlockAtInSecondTimestamp - prevUnlockAtInSecondTimestamp;
75
- if (totalLockDuration > MAX_LOCK_DURATION - UNLOCK_ROUND_DURATION) {
76
- throw new Error(
77
- `Maximum lock period is ~4 years (${MAX_LOCK_ROUNDS - 1} days)`
78
- );
79
- }
80
- }
81
- }
82
- };
83
-
84
- export const checkExtendLockPeriod = (
85
- lockPeriodInDays: number,
86
- newUnlockAtInSecondTimestamp: number,
87
- prevUnlockAtInSecondTimestamp?: number
88
- ) => {
89
- checkVesca(prevUnlockAtInSecondTimestamp);
90
-
91
- if (lockPeriodInDays <= 0) {
92
- throw new Error('Lock period must be greater than 0');
93
- }
94
-
95
- const isInitialLock = !prevUnlockAtInSecondTimestamp;
96
- const isLockExpired =
97
- !isInitialLock &&
98
- prevUnlockAtInSecondTimestamp * 1000 <= new Date().getTime();
99
- if (isLockExpired) {
100
- throw new Error('veSca is expired, use renewExpiredVeScaQuick instead');
101
- }
102
-
103
- if (prevUnlockAtInSecondTimestamp) {
104
- const totalLockDuration =
105
- newUnlockAtInSecondTimestamp - prevUnlockAtInSecondTimestamp!;
106
- if (totalLockDuration > MAX_LOCK_DURATION - UNLOCK_ROUND_DURATION) {
107
- throw new Error(
108
- `Maximum lock period is ~4 years (${MAX_LOCK_ROUNDS - 1} days)`
119
+ // for topup and extend lock period
120
+ if (newUnlockAtInSecondTimestamp && lockPeriodInDays) {
121
+ checkExtendLockPeriod(
122
+ lockPeriodInDays,
123
+ newUnlockAtInSecondTimestamp,
124
+ prevUnlockAtInMillisTimestamp
109
125
  );
110
126
  }
111
127
  }
@@ -113,18 +129,19 @@ export const checkExtendLockPeriod = (
113
129
 
114
130
  export const checkExtendLockAmount = (
115
131
  scaAmount: number,
116
- prevUnlockAtInSecondTimestamp?: number
132
+ prevUnlockAtInMillisTimestamp?: number
117
133
  ) => {
118
- checkVesca(prevUnlockAtInSecondTimestamp);
134
+ checkVesca(prevUnlockAtInMillisTimestamp);
135
+ checkVescaExpired(prevUnlockAtInMillisTimestamp!);
119
136
 
120
137
  if (scaAmount < MIN_TOP_UP_AMOUNT) {
121
138
  throw new Error('Minimum top up amount is 1 SCA');
122
139
  }
123
140
 
124
- const isInitialLock = !prevUnlockAtInSecondTimestamp;
141
+ const isInitialLock = !prevUnlockAtInMillisTimestamp;
125
142
  const isLockExpired =
126
- !isInitialLock &&
127
- prevUnlockAtInSecondTimestamp * 1000 <= new Date().getTime();
143
+ !isInitialLock && prevUnlockAtInMillisTimestamp <= new Date().getTime();
144
+
128
145
  if (isLockExpired) {
129
146
  throw new Error('veSca is expired, use renewExpiredVeScaQuick instead');
130
147
  }
@@ -133,9 +150,14 @@ export const checkExtendLockAmount = (
133
150
  export const checkRenewExpiredVeSca = (
134
151
  scaAmount: number,
135
152
  lockPeriodInDays: number,
136
- prevUnlockAtInSecondTimestamp?: number
153
+ prevUnlockAtInMillisTimestamp?: number
137
154
  ) => {
138
- checkVesca(prevUnlockAtInSecondTimestamp);
155
+ if (
156
+ !prevUnlockAtInMillisTimestamp ||
157
+ prevUnlockAtInMillisTimestamp > new Date().getTime()
158
+ ) {
159
+ throw new Error('Renew method can only be used for expired veSca');
160
+ }
139
161
 
140
162
  if (scaAmount < MIN_INITIAL_LOCK_AMOUNT) {
141
163
  throw new Error('Minimum lock amount for renewing expired vesca 10 SCA');
@@ -148,9 +170,3 @@ export const checkRenewExpiredVeSca = (
148
170
  );
149
171
  }
150
172
  };
151
-
152
- export const checkVesca = (prevUnlockAtInSecondTimestamp?: number) => {
153
- if (prevUnlockAtInSecondTimestamp === undefined) {
154
- throw new Error('veSca not found');
155
- }
156
- };