@scallop-io/sui-scallop-sdk 1.5.3 → 2.0.0-alpha.2

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 (60) hide show
  1. package/dist/index.d.mts +451 -602
  2. package/dist/index.d.ts +451 -602
  3. package/dist/index.js +29 -60
  4. package/dist/index.mjs +6 -6
  5. package/package.json +1 -1
  6. package/src/builders/loyaltyProgramBuilder.ts +5 -3
  7. package/src/builders/oracle.ts +10 -24
  8. package/src/builders/referralBuilder.ts +5 -9
  9. package/src/builders/sCoinBuilder.ts +9 -8
  10. package/src/builders/spoolBuilder.ts +4 -6
  11. package/src/constants/common.ts +114 -126
  12. package/src/constants/index.ts +0 -5
  13. package/src/constants/pyth.ts +25 -34
  14. package/src/constants/queryKeys.ts +2 -0
  15. package/src/models/index.ts +1 -0
  16. package/src/models/scallop.ts +23 -19
  17. package/src/models/scallopAddress.ts +7 -4
  18. package/src/models/scallopBuilder.ts +36 -41
  19. package/src/models/scallopCache.ts +1 -1
  20. package/src/models/scallopClient.ts +93 -94
  21. package/src/models/scallopConstants.ts +342 -0
  22. package/src/models/scallopIndexer.ts +11 -24
  23. package/src/models/scallopQuery.ts +70 -77
  24. package/src/models/scallopUtils.ts +122 -249
  25. package/src/queries/borrowIncentiveQuery.ts +21 -56
  26. package/src/queries/borrowLimitQuery.ts +3 -6
  27. package/src/queries/coreQuery.ts +94 -112
  28. package/src/queries/flashloanFeeQuery.ts +86 -0
  29. package/src/queries/isolatedAssetQuery.ts +12 -11
  30. package/src/queries/poolAddressesQuery.ts +187 -112
  31. package/src/queries/portfolioQuery.ts +65 -67
  32. package/src/queries/priceQuery.ts +16 -22
  33. package/src/queries/sCoinQuery.ts +15 -16
  34. package/src/queries/spoolQuery.ts +49 -59
  35. package/src/queries/supplyLimitQuery.ts +2 -6
  36. package/src/queries/xOracleQuery.ts +4 -15
  37. package/src/types/address.ts +12 -18
  38. package/src/types/builder/borrowIncentive.ts +2 -3
  39. package/src/types/builder/core.ts +20 -27
  40. package/src/types/builder/index.ts +1 -2
  41. package/src/types/builder/referral.ts +4 -8
  42. package/src/types/builder/sCoin.ts +4 -8
  43. package/src/types/builder/spool.ts +7 -10
  44. package/src/types/constant/common.ts +43 -49
  45. package/src/types/constant/enum.ts +15 -27
  46. package/src/types/constant/xOracle.ts +3 -5
  47. package/src/types/model.ts +49 -28
  48. package/src/types/query/borrowIncentive.ts +7 -24
  49. package/src/types/query/core.ts +8 -18
  50. package/src/types/query/portfolio.ts +8 -17
  51. package/src/types/query/spool.ts +5 -11
  52. package/src/types/utils.ts +1 -21
  53. package/src/utils/core.ts +1 -1
  54. package/src/utils/query.ts +13 -20
  55. package/src/utils/util.ts +6 -84
  56. package/src/constants/coinGecko.ts +0 -34
  57. package/src/constants/enum.ts +0 -268
  58. package/src/constants/flashloan.ts +0 -18
  59. package/src/constants/poolAddress.ts +0 -898
  60. package/src/models/scallopPrice.ts +0 -0
@@ -0,0 +1,86 @@
1
+ import { SuiObjectData } from '@mysten/sui/client';
2
+ import { ScallopUtils } from 'src/models';
3
+ import { ScallopConstants } from 'src/models/scallopConstants';
4
+
5
+ const FLASHLOAN_FEES_TABLE_ID =
6
+ '0x00481a93b819d744a7d79ecdc6c62c74f2f7cb4779316c4df640415817ac61bb' as const;
7
+
8
+ export const queryFlashLoanFees = async (
9
+ utils: ScallopUtils,
10
+ assetNames: string[],
11
+ feeRate: number
12
+ ) => {
13
+ const assetNamesSet = new Set(assetNames);
14
+ const assetTypeMap = Object.fromEntries(
15
+ Object.entries(utils.constants.coinTypeToCoinNameMap).filter(([_, value]) =>
16
+ assetNamesSet.has(value!)
17
+ )
18
+ );
19
+
20
+ let cursor: string | null | undefined = null;
21
+ let nextPage: boolean = false;
22
+ const flashloanFeeObjects: SuiObjectData[] = [];
23
+ do {
24
+ const resp = await utils.cache.queryGetDynamicFields({
25
+ parentId: FLASHLOAN_FEES_TABLE_ID,
26
+ limit: 10,
27
+ cursor,
28
+ });
29
+
30
+ if (!resp) break;
31
+ const { data, hasNextPage, nextCursor } = resp;
32
+ // get the dynamic object ids
33
+ const dynamicFieldObjectIds =
34
+ data
35
+ .filter((field) => {
36
+ const assetType = `0x${(field.name.value as any).name as string}`;
37
+ return !!assetTypeMap[assetType];
38
+ })
39
+ .map((field) => field.objectId) ?? [];
40
+
41
+ flashloanFeeObjects.push(
42
+ ...(await utils.cache.queryGetObjects(dynamicFieldObjectIds))
43
+ );
44
+ nextPage = hasNextPage;
45
+ cursor = nextCursor;
46
+ } while (nextPage);
47
+
48
+ return flashloanFeeObjects.reduce(
49
+ (prev, curr) => {
50
+ if (curr.content?.dataType === 'moveObject') {
51
+ const assetType = `0x${(curr.content.fields as any).name.fields.name}`;
52
+ const assetName = assetTypeMap[assetType];
53
+ if (!assetName) return prev;
54
+
55
+ const objectFields = curr.content.fields as any;
56
+ const feeNumerator = +objectFields.value;
57
+ prev[assetName] = feeNumerator / feeRate;
58
+ }
59
+ return prev;
60
+ },
61
+ {} as Record<string, number>
62
+ );
63
+ };
64
+
65
+ export const parseFlashloanFeeObjects = (
66
+ constants: ScallopConstants,
67
+ objects: SuiObjectData[],
68
+ feeRate: number
69
+ ) => {
70
+ const assetTypeMap = constants.coinTypeToCoinNameMap;
71
+ return objects.reduce(
72
+ (prev, curr) => {
73
+ if (curr.content?.dataType === 'moveObject') {
74
+ const assetType = `0x${(curr.content.fields as any).name.fields.name}`;
75
+ const assetName = assetTypeMap[assetType];
76
+ if (!assetName) return prev;
77
+
78
+ const objectFields = curr.content.fields as any;
79
+ const feeNumerator = +objectFields.value;
80
+ prev[assetName] = feeNumerator / feeRate;
81
+ }
82
+ return prev;
83
+ },
84
+ {} as Record<string, number>
85
+ );
86
+ };
@@ -1,8 +1,6 @@
1
1
  import { DynamicFieldInfo, DynamicFieldName } from '@mysten/sui/client';
2
2
  import { ScallopQuery, ScallopUtils } from '../models';
3
- import { SupportPoolCoins } from '../types';
4
3
  import { z as zod } from 'zod';
5
- import { POOL_ADDRESSES, SUPPORT_POOLS } from 'src/constants';
6
4
 
7
5
  const isolatedAssetZod = zod.object({
8
6
  dataType: zod.string(),
@@ -28,14 +26,16 @@ const isolatedAssetKeyType = `0xe7dbb371a9595631f7964b7ece42255ad0e738cc85fe6da2
28
26
  * @returns list of isolated assets coin types
29
27
  */
30
28
  export const getIsolatedAssets = async (
31
- query: ScallopQuery
29
+ query: ScallopQuery,
30
+ useOnChainQuery: boolean = false
32
31
  ): Promise<string[]> => {
33
- if (SUPPORT_POOLS.every((t) => !!POOL_ADDRESSES[t])) {
34
- return SUPPORT_POOLS.filter(
35
- (t): t is typeof t & { coinType: string; isolatedAssetKey: string } =>
36
- !!POOL_ADDRESSES[t]?.isolatedAssetKey && !!POOL_ADDRESSES[t]?.coinType
37
- ).map((t) => POOL_ADDRESSES[t as SupportPoolCoins]?.coinType!);
32
+ if (!useOnChainQuery) {
33
+ return query.utils
34
+ .getSupportedPoolAddresses()
35
+ .filter((t) => !!t.isolatedAssetKey)
36
+ .map((t) => t.coinName);
38
37
  }
38
+
39
39
  try {
40
40
  const marketObject = query.address.get('core.market');
41
41
  const isolatedAssets: string[] = [];
@@ -86,10 +86,11 @@ export const getIsolatedAssets = async (
86
86
  */
87
87
  export const isIsolatedAsset = async (
88
88
  utils: ScallopUtils,
89
- coinName: SupportPoolCoins
89
+ coinName: string
90
90
  ): Promise<boolean> => {
91
- if (POOL_ADDRESSES[coinName]) {
92
- return !!POOL_ADDRESSES[coinName].isolatedAssetKey;
91
+ const assetInPoolAddresses = utils.constants.poolAddresses[coinName];
92
+ if (assetInPoolAddresses) {
93
+ return !!assetInPoolAddresses.isolatedAssetKey;
93
94
  }
94
95
 
95
96
  const marketObject = utils.address.get('core.market');
@@ -1,59 +1,118 @@
1
- import { PYTH_FEED_IDS } from 'src/constants';
2
- import { ScallopQuery } from 'src/models';
3
- import { OptionalKeys, SupportPoolCoins } from 'src/types';
1
+ import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
2
+ import { AddressesInterface, OptionalKeys, PoolAddress } from 'src/types';
4
3
 
5
- export const getAllAddresses = async (
6
- query: ScallopQuery,
7
- pools: SupportPoolCoins[]
4
+ const RPC_PROVIDERS = [
5
+ getFullnodeUrl('mainnet'),
6
+ 'https://sui-mainnet.public.blastapi.io',
7
+ 'https://sui-mainnet-ca-2.cosmostation.io',
8
+ 'https://sui-mainnet-eu-4.cosmostation.io',
9
+ 'https://sui-mainnet-endpoint.blockvision.org',
10
+ 'https://sui-rpc.publicnode.com',
11
+ 'https://sui-mainnet-rpc.allthatnode.com',
12
+ 'https://mainnet.suiet.app',
13
+ 'https://mainnet.sui.rpcpool.com',
14
+ 'https://sui1mainnet-rpc.chainode.tech',
15
+ 'https://fullnode.mainnet.apis.scallop.io',
16
+ 'https://sui-mainnet-us-2.cosmostation.io',
17
+ ];
18
+
19
+ const tryRequest = async <T>(fn: (client: SuiClient) => T) => {
20
+ for (const rpc of RPC_PROVIDERS) {
21
+ try {
22
+ const res = await fn(new SuiClient({ url: rpc }));
23
+ return res;
24
+ } catch (e: any) {
25
+ if (e.status !== 429) {
26
+ throw e;
27
+ }
28
+ await new Promise((resolve) => setTimeout(resolve, 500));
29
+ }
30
+ }
31
+ throw new Error('Failed to fetch data');
32
+ };
33
+
34
+ const queryFlashloanFeeObjectIds = async (
35
+ client: SuiClient,
36
+ coinTypeMap: Set<string>, // set of coin types
37
+ flashloanFeeTableId: string = '0x00481a93b819d744a7d79ecdc6c62c74f2f7cb4779316c4df640415817ac61bb'
8
38
  ) => {
9
- const results: OptionalKeys<
10
- Record<
11
- SupportPoolCoins,
12
- {
13
- coinName: string;
14
- symbol: string;
15
- lendingPoolAddress?: string;
16
- collateralPoolAddress?: string; // not all pool has collateral
17
- borrowDynamic?: string;
18
- spoolReward?: string;
19
- spool?: string;
20
- sCoinType?: string;
21
- sCoinName?: string;
22
- sCoinSymbol?: string;
23
- sCoinTreasury?: string;
24
- interestModel?: string;
25
- riskModel?: string;
26
- borrowFeeKey?: string;
27
- supplyLimitKey?: string;
28
- borrowLimitKey?: string;
29
- isolatedAssetKey?: string;
30
- coinMetadataId?: string;
31
- borrowIncentivePoolId?: string;
32
- coinType?: string;
33
- sCoinMetadataId?: string;
34
- spoolName?: string;
35
- decimals: number;
36
- pythFeed?: string;
37
- pythFeedObjectId?: string;
39
+ let cursor: string | null | undefined = null;
40
+ let nextPage: boolean = false;
41
+ const flashloanFeeObjectIds: Record<string, string> = {}; // <coinType, flashloanFeeObjectId>
42
+
43
+ do {
44
+ const resp = await client.getDynamicFields({
45
+ parentId: flashloanFeeTableId,
46
+ limit: 10,
47
+ cursor,
48
+ });
49
+ if (!resp) break;
50
+ const { data, hasNextPage, nextCursor } = resp;
51
+ // get the dynamic object ids
52
+ data.forEach((field) => {
53
+ const assetType = `0x${(field.name.value as any).name as string}`;
54
+ if (coinTypeMap.has(assetType)) {
55
+ flashloanFeeObjectIds[assetType] = field.objectId;
38
56
  }
39
- >
40
- > = {};
57
+ });
58
+ nextPage = hasNextPage;
59
+ cursor = nextCursor;
60
+ } while (nextPage);
61
+
62
+ return flashloanFeeObjectIds;
63
+ };
41
64
 
42
- const marketId = query.address.get('core.market');
43
- const marketObject = (await query.cache.queryGetObject(marketId))?.data;
65
+ export const getPoolAddresses = async (
66
+ addressId: string,
67
+ poolNames: string[] = []
68
+ ) => {
69
+ const poolNameSet = new Set(poolNames);
70
+ const results: OptionalKeys<Record<string, PoolAddress>> = {};
44
71
 
45
- if (!(marketObject && marketObject.content?.dataType === 'moveObject'))
46
- throw new Error(`Failed to fetch marketObject`);
72
+ // fetch the Address API
73
+ const URL = 'https://sui.apis.scallop.io';
74
+ const addressApiResponse = (
75
+ await fetch(`${URL}/addresses/${addressId}`).then((res) => res.json())
76
+ ).mainnet as unknown as AddressesInterface;
47
77
 
48
- const fields = marketObject.content.fields as any;
78
+ if (!addressApiResponse) throw new Error('Failed to fetch addresses');
79
+
80
+ // Compose pools based on addresses
81
+ const pools = Object.keys(addressApiResponse.core.coins);
82
+ if (pools.length === 0) throw new Error('Pools empty');
49
83
 
50
84
  const coinTypesPairs = pools.reduce(
51
- (acc, pool) => {
52
- acc.push([pool, query.utils.parseCoinType(pool).substring(2)]);
85
+ (acc, poolName) => {
86
+ if (poolNameSet.size > 0 && !poolNameSet.has(poolName)) return acc;
87
+ const value =
88
+ addressApiResponse.core.coins[
89
+ poolName as keyof typeof addressApiResponse.core.coins
90
+ ];
91
+ if (value && value.coinType) {
92
+ acc.push([poolName, value.coinType]);
93
+ }
53
94
  return acc;
54
95
  },
55
- [] as [SupportPoolCoins, string][]
96
+ [] as [string, string][]
56
97
  );
98
+ if (coinTypesPairs.length === 0) throw new Error('No coinTypesPairs');
99
+
100
+ const marketId = addressApiResponse.core.market;
101
+ const marketObject = (
102
+ await tryRequest(async (client) => {
103
+ return await client.getObject({
104
+ id: marketId,
105
+ options: {
106
+ showContent: true,
107
+ },
108
+ });
109
+ })
110
+ ).data;
111
+
112
+ if (!(marketObject && marketObject.content?.dataType === 'moveObject'))
113
+ throw new Error(`Failed to fetch marketObject`);
114
+
115
+ const fields = marketObject.content.fields as any;
57
116
 
58
117
  const balanceSheetParentId =
59
118
  fields.vault.fields.balance_sheets.fields.table.fields.id.id;
@@ -79,111 +138,127 @@ export const getAllAddresses = async (
79
138
  type: string,
80
139
  value: any
81
140
  ) => {
82
- try {
83
- return (
84
- await query.cache.queryGetDynamicFieldObject({
141
+ return (
142
+ await tryRequest(async (client) => {
143
+ return await client.getDynamicFieldObject({
85
144
  parentId,
86
145
  name: {
87
146
  type,
88
147
  value,
89
148
  },
90
- })
91
- )?.data?.objectId;
92
- } catch (e: any) {
93
- console.error(e.message);
94
- return undefined;
95
- }
149
+ });
150
+ })
151
+ ).data?.objectId;
96
152
  };
97
153
 
154
+ // query flashloan fee objects first
155
+ const flashloanFeeObjectIds = await tryRequest(async (client) => {
156
+ return await queryFlashloanFeeObjectIds(
157
+ client,
158
+ new Set(coinTypesPairs.map(([, coinType]) => coinType))
159
+ );
160
+ });
161
+
98
162
  await Promise.all(
99
163
  coinTypesPairs.map(async ([coinName, coinType]) => {
164
+ const coinTypeKey = coinType.slice(2);
100
165
  const addresses = await Promise.all([
101
166
  fetchDynamicObject(balanceSheetParentId, ADDRESS_TYPE, {
102
- name: coinType,
167
+ name: coinTypeKey,
103
168
  }),
104
169
  fetchDynamicObject(collateralStatsParentId, ADDRESS_TYPE, {
105
- name: coinType,
170
+ name: coinTypeKey,
106
171
  }),
107
172
  fetchDynamicObject(borrowDynamicsParentid, ADDRESS_TYPE, {
108
- name: coinType,
173
+ name: coinTypeKey,
109
174
  }),
110
175
  fetchDynamicObject(interestModelParentId, ADDRESS_TYPE, {
111
- name: coinType,
176
+ name: coinTypeKey,
112
177
  }),
113
178
  fetchDynamicObject(riskModelParentId, ADDRESS_TYPE, {
114
- name: coinType,
179
+ name: coinTypeKey,
115
180
  }),
116
- fetchDynamicObject(marketId, BORROW_FEE_TYPE, coinType),
117
- fetchDynamicObject(marketId, SUPPLY_LIMIT_TYPE, coinType),
118
- fetchDynamicObject(marketId, BORROW_LIMIT_TYPE, coinType),
119
- fetchDynamicObject(marketId, ISOLATED_ASSET_KEY, coinType),
181
+ fetchDynamicObject(marketId, BORROW_FEE_TYPE, coinTypeKey),
182
+ fetchDynamicObject(marketId, SUPPLY_LIMIT_TYPE, coinTypeKey),
183
+ fetchDynamicObject(marketId, BORROW_LIMIT_TYPE, coinTypeKey),
184
+ fetchDynamicObject(marketId, ISOLATED_ASSET_KEY, coinTypeKey),
120
185
  ]);
121
186
 
122
- const spool = query.address.get(
123
- // @ts-ignore
124
- `spool.pools.s${coinName}.id`
125
- );
126
- const rewardPool = query.address.get(
127
- // @ts-ignore
128
- `spool.pools.s${coinName}.rewardPoolId`
129
- );
130
- const sCoinType = query.address.get(
131
- // @ts-ignore
132
- `scoin.coins.s${coinName}.coinType`
133
- );
134
- const sCoinName = sCoinType
135
- ? query.utils.parseSCoinName(coinName)
136
- : undefined;
137
- const sCoinSymbol = sCoinName
138
- ? query.utils.parseSymbol(sCoinName)
139
- : undefined;
140
- const sCoinTreasury = query.address.get(
141
- // @ts-ignore
142
- `scoin.coins.s${coinName}.treasury`
143
- );
144
- const coinMetadataId = query.address.get(
145
- `core.coins.${coinName}.metaData`
146
- );
147
- const sCoinMetadataId = query.address.get(
187
+ // @ts-ignore
188
+ const { symbol, metaData: coinMetadataId } =
189
+ addressApiResponse.core.coins[coinName];
190
+
191
+ let spoolData = undefined;
192
+ const _spoolData = addressApiResponse.spool.pools[`s${coinName}`];
193
+ // @ts-ignore
194
+ if (_spoolData) {
148
195
  // @ts-ignore
149
- `scoin.coins.s${coinName}.metaData`
150
- );
196
+ const { id: spool, rewardPoolId: spoolReward } = _spoolData;
197
+ spoolData = {
198
+ spool,
199
+ spoolReward,
200
+ coinMetadataId,
201
+ };
202
+ }
151
203
 
152
- const pythFeed = PYTH_FEED_IDS[coinName];
153
- const pythFeedObjectId = query.address.get(
204
+ let sCoinData = undefined;
205
+ const sCoinName = `s${coinName}`;
206
+ const _sCoinData = addressApiResponse.scoin.coins[sCoinName];
207
+ if (_sCoinData) {
208
+ const {
209
+ coinType: sCoinType,
210
+ treasury: sCoinTreasury,
211
+ metaData: sCoinMetadataId,
212
+ symbol: sCoinSymbol,
213
+ } = _sCoinData;
214
+ sCoinData = {
215
+ sCoinType,
216
+ sCoinTreasury,
217
+ sCoinMetadataId,
218
+ sCoinSymbol,
219
+ };
220
+ }
221
+
222
+ const { feed: pythFeed, feedObject: pythFeedObjectId } =
154
223
  //@ts-ignore
155
- `core.coins.${coinName}.oracle.pyth.feedObject`
156
- );
157
- const decimals = query.utils.getCoinDecimal(coinName);
158
- const spoolName = spool ? `s${coinName}` : undefined;
159
- results[coinName as SupportPoolCoins] = {
224
+ addressApiResponse.core.coins[coinName].oracle.pyth;
225
+
226
+ const decimals =
227
+ (
228
+ await tryRequest(async (client) => {
229
+ return await client.getCoinMetadata({
230
+ coinType: coinType,
231
+ });
232
+ })
233
+ )?.decimals ?? 0;
234
+ const spoolName = spoolData ? `s${coinName}` : undefined;
235
+
236
+ // @TODO: add query for flashloanFeeObject
237
+ results[coinName] = {
160
238
  coinName,
161
- symbol: query.utils.parseSymbol(coinName),
162
- lendingPoolAddress: addresses[0],
163
- collateralPoolAddress: addresses[1],
164
- borrowDynamic: addresses[2],
165
- interestModel: addresses[3],
239
+ symbol,
240
+ lendingPoolAddress: addresses[0] ?? '',
241
+ collateralPoolAddress: addresses[1] ?? '',
242
+ borrowDynamic: addresses[2] ?? '',
243
+ interestModel: addresses[3] ?? '',
166
244
  riskModel: addresses[4],
167
- borrowFeeKey: addresses[5],
245
+ borrowFeeKey: addresses[5] ?? '',
168
246
  supplyLimitKey: addresses[6],
169
247
  borrowLimitKey: addresses[7],
170
248
  isolatedAssetKey: addresses[8],
171
- spool,
172
- spoolReward: rewardPool,
173
- sCoinTreasury,
174
- sCoinType,
249
+ ...(spoolData ?? {}),
250
+ ...(sCoinData ?? {}),
175
251
  sCoinName,
176
- sCoinSymbol,
177
252
  coinMetadataId,
178
- coinType: `0x${coinType}`,
179
- sCoinMetadataId,
253
+ coinType,
180
254
  spoolName,
181
255
  decimals,
182
256
  pythFeed,
183
257
  pythFeedObjectId,
258
+ flashloanFeeObject: flashloanFeeObjectIds[coinType] ?? '',
184
259
  };
185
260
 
186
- await new Promise((resolve) => setTimeout(resolve, 500));
261
+ await new Promise((resolve) => setTimeout(resolve, 1000));
187
262
  })
188
263
  );
189
264