@scallop-io/sui-scallop-sdk 1.4.6 → 1.4.8

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 (37) hide show
  1. package/dist/constants/index.d.ts +0 -1
  2. package/dist/constants/poolAddress.d.ts +1 -0
  3. package/dist/index.d.ts +0 -1
  4. package/dist/index.js +1657 -1472
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +1866 -1677
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/models/scallop.d.ts +1 -2
  9. package/dist/models/scallopCache.d.ts +16 -10
  10. package/dist/models/scallopQuery.d.ts +75 -3
  11. package/dist/queries/poolAddressesQuery.d.ts +2 -0
  12. package/dist/queries/portfolioQuery.d.ts +66 -3
  13. package/dist/types/model.d.ts +8 -2
  14. package/dist/utils/index.d.ts +0 -2
  15. package/package.json +1 -1
  16. package/src/constants/index.ts +0 -1
  17. package/src/constants/poolAddress.ts +93 -25
  18. package/src/index.ts +0 -1
  19. package/src/models/scallop.ts +8 -13
  20. package/src/models/scallopAddress.ts +2 -9
  21. package/src/models/scallopBuilder.ts +3 -6
  22. package/src/models/scallopCache.ts +88 -47
  23. package/src/models/scallopClient.ts +3 -6
  24. package/src/models/scallopIndexer.ts +1 -5
  25. package/src/models/scallopQuery.ts +49 -16
  26. package/src/models/scallopUtils.ts +7 -11
  27. package/src/queries/coreQuery.ts +4 -4
  28. package/src/queries/poolAddressesQuery.ts +6 -0
  29. package/src/queries/portfolioQuery.ts +238 -12
  30. package/src/queries/sCoinQuery.ts +2 -2
  31. package/src/types/model.ts +13 -2
  32. package/src/utils/index.ts +0 -2
  33. package/src/utils/indexer.ts +3 -1
  34. package/dist/constants/tokenBucket.d.ts +0 -2
  35. package/dist/utils/tokenBucket.d.ts +0 -11
  36. package/src/constants/tokenBucket.ts +0 -2
  37. package/src/utils/tokenBucket.ts +0 -68
@@ -26,9 +26,12 @@ import type {
26
26
  ObligationBorrowIcentiveReward,
27
27
  SupportBorrowIncentiveRewardCoins,
28
28
  SupportAssetCoins,
29
+ MarketPools,
30
+ MarketCollaterals,
29
31
  } from '../types';
30
32
  import { SuiObjectRef } from '@mysten/sui/client';
31
33
  import { queryMultipleObjects } from './objectsQuery';
34
+ import { normalizeStructTag, SUI_TYPE_ARG } from '@scallop-io/sui-kit';
32
35
 
33
36
  /**
34
37
  * Get user lending infomation for specific pools.
@@ -43,6 +46,8 @@ export const getLendings = async (
43
46
  query: ScallopQuery,
44
47
  poolCoinNames: SupportPoolCoins[] = [...SUPPORT_POOLS],
45
48
  ownerAddress?: string,
49
+ marketPools?: MarketPools,
50
+ coinPrices?: CoinPrices,
46
51
  indexer: boolean = false
47
52
  ) => {
48
53
  const marketCoinNames = poolCoinNames.map((poolCoinName) =>
@@ -52,13 +57,15 @@ export const getLendings = async (
52
57
  (SUPPORT_SPOOLS as readonly SupportMarketCoins[]).includes(marketCoinName)
53
58
  ) as SupportStakeMarketCoins[];
54
59
 
55
- const coinPrices = await query.utils.getCoinPrices();
56
- const marketPools = (
57
- await query.getMarketPools(poolCoinNames, {
58
- indexer,
59
- coinPrices,
60
- })
61
- ).pools;
60
+ coinPrices = coinPrices ?? (await query.utils.getCoinPrices());
61
+ marketPools =
62
+ marketPools ??
63
+ (
64
+ await query.getMarketPools(poolCoinNames, {
65
+ indexer,
66
+ coinPrices,
67
+ })
68
+ ).pools;
62
69
 
63
70
  const spools = await query.getSpools(stakeMarketCoinNames, {
64
71
  indexer,
@@ -311,12 +318,19 @@ export const getLending = async (
311
318
  export const getObligationAccounts = async (
312
319
  query: ScallopQuery,
313
320
  ownerAddress?: string,
321
+ market?: {
322
+ pools: MarketPools;
323
+ collaterals: MarketCollaterals;
324
+ },
325
+ coinPrices?: CoinPrices,
314
326
  indexer: boolean = false
315
327
  ) => {
316
- const market = await query.getMarketPools(undefined, { indexer });
317
- const coinPrices = await query.getAllCoinPrices({
318
- marketPools: market.pools,
319
- });
328
+ market = market ?? (await query.getMarketPools(undefined, { indexer }));
329
+ coinPrices =
330
+ coinPrices ??
331
+ (await query.getAllCoinPrices({
332
+ marketPools: market.pools,
333
+ }));
320
334
  const [coinAmounts, obligations] = await Promise.all([
321
335
  query.getCoinAmounts(undefined, ownerAddress),
322
336
  query.getObligations(ownerAddress),
@@ -379,7 +393,6 @@ export const getObligationAccount = async (
379
393
  query.queryObligation(obligation),
380
394
  query.getBorrowIncentivePools(undefined, {
381
395
  coinPrices,
382
- indexer,
383
396
  marketPools: market.pools,
384
397
  }),
385
398
  query.getBorrowIncentiveAccounts(obligation),
@@ -838,3 +851,216 @@ export const getTotalValueLocked = async (
838
851
 
839
852
  return tvl;
840
853
  };
854
+
855
+ /**
856
+ * Get user portfolio by wallet address
857
+ */
858
+ export const getUserPortfolio = async (
859
+ query: ScallopQuery,
860
+ walletAddress: string,
861
+ indexer: boolean = false
862
+ ) => {
863
+ const coinPrices = await query.utils.getCoinPrices();
864
+ const market = await query.getMarketPools(undefined, { indexer, coinPrices });
865
+
866
+ const [lendings, obligationAccounts, borrowIncentivePools, veScas] =
867
+ await Promise.all([
868
+ query.getLendings(undefined, walletAddress, {
869
+ indexer,
870
+ marketPools: market.pools,
871
+ coinPrices,
872
+ }),
873
+ query.getObligationAccounts(walletAddress, {
874
+ indexer,
875
+ market: market,
876
+ coinPrices,
877
+ }),
878
+ query.getBorrowIncentivePools(undefined, {
879
+ marketPools: market.pools,
880
+ coinPrices,
881
+ }),
882
+ query.getVeScas({ walletAddress, excludeEmpty: true }),
883
+ ]);
884
+
885
+ // get pending rewards (spool and borrow incentive)
886
+ const parsedLendings = Object.values(lendings)
887
+ .filter((t) => t.availableWithdrawCoin > 0)
888
+ .map((lending) => ({
889
+ suppliedCoin: lending.availableWithdrawCoin,
890
+ suppliedValue: lending.suppliedValue,
891
+ stakedCoin: lending.availableUnstakeCoin,
892
+ coinName: lending.coinName,
893
+ symbol: lending.symbol,
894
+ coinType: lending.coinType,
895
+ coinPrice: lending.coinPrice,
896
+ coinDecimals: lending.coinDecimal,
897
+ supplyApr: lending.supplyApr,
898
+ supplyApy: lending.supplyApy,
899
+ incentiveApr: isFinite(lending.rewardApr) ? lending.rewardApr : 0,
900
+ }));
901
+
902
+ const parsedObligationAccounts = Object.values(obligationAccounts)
903
+ .filter(
904
+ (t): t is NonNullable<typeof t> =>
905
+ !!t && t.totalBorrowedValueWithWeight > 0
906
+ )
907
+ .map((obligationAccount) => {
908
+ return {
909
+ obligationId: obligationAccount.obligationId,
910
+ totalDebtsInUsd: obligationAccount.totalBorrowedValueWithWeight,
911
+ totalCollateralInUsd: obligationAccount.totalDepositedValue,
912
+ riskLevel: obligationAccount.totalRiskLevel,
913
+ availableCollateralInUsd:
914
+ obligationAccount.totalAvailableCollateralValue,
915
+ totalUnhealthyCollateralInUsd:
916
+ obligationAccount.totalUnhealthyCollateralValue,
917
+ borrowedPools: Object.values(obligationAccount.debts)
918
+ .filter((debt) => debt.borrowedCoin > 0)
919
+ .map((debt) => ({
920
+ coinName: debt.coinName,
921
+ symbol: debt.symbol,
922
+ coinDecimals: debt.coinDecimal,
923
+ coinType: debt.coinType,
924
+ coinPrice: debt.coinPrice,
925
+ borrowedCoin: debt.borrowedCoin,
926
+ borrowedValueInUsd: debt.borrowedValueWithWeight,
927
+ borrowApr: market.pools[debt.coinName]?.borrowApr,
928
+ borrowApy: market.pools[debt.coinName]?.borrowApy,
929
+ incentiveInfos: Object.values(
930
+ borrowIncentivePools[debt.coinName]?.points ?? {}
931
+ )
932
+ .filter((t) => isFinite(t.rewardApr))
933
+ .map((t) => ({
934
+ coinName: t.coinName,
935
+ symbol: t.symbol,
936
+ coinType: t.coinType,
937
+ incentiveApr: t.rewardApr,
938
+ })),
939
+ })),
940
+ };
941
+ });
942
+
943
+ const pendingLendingRewards = Object.values(lendings).reduce(
944
+ (acc, reward) => {
945
+ if (reward.availableClaimCoin === 0) return acc;
946
+ if (!acc[reward.symbol]) {
947
+ acc[reward.symbol] = {
948
+ symbol: reward.symbol,
949
+ coinType: normalizeStructTag(SUI_TYPE_ARG), // @TODO: for now lending reward is all in SUI
950
+ coinPrice: reward.coinPrice,
951
+ pendingRewardInCoin: reward.availableClaimCoin,
952
+ };
953
+ } else {
954
+ acc[reward.symbol].pendingRewardInCoin += reward.availableClaimCoin;
955
+ }
956
+ return acc;
957
+ },
958
+ {} as Record<
959
+ string,
960
+ {
961
+ coinType: string;
962
+ symbol: string;
963
+ coinPrice: number;
964
+ pendingRewardInCoin: number;
965
+ }
966
+ >
967
+ );
968
+
969
+ const pendingBorrowIncentiveRewards = Object.values(obligationAccounts)
970
+ .filter((t): t is NonNullable<typeof t> => !!t)
971
+ .reduce(
972
+ (acc, curr) => {
973
+ Object.values(curr.borrowIncentives).forEach((incentive) => {
974
+ incentive.rewards.forEach((reward) => {
975
+ if (reward.availableClaimCoin === 0) return acc;
976
+ if (!acc[reward.coinName]) {
977
+ acc[reward.coinName] = {
978
+ symbol: reward.symbol,
979
+ coinType: reward.coinType,
980
+ coinPrice: reward.coinPrice,
981
+ pendingRewardInCoin: reward.availableClaimCoin,
982
+ };
983
+ } else {
984
+ acc[reward.coinName].pendingRewardInCoin +=
985
+ reward.availableClaimCoin;
986
+ }
987
+ });
988
+ });
989
+ return acc;
990
+ },
991
+ {} as Record<
992
+ string,
993
+ {
994
+ coinType: string;
995
+ symbol: string;
996
+ coinPrice: number;
997
+ pendingRewardInCoin: number;
998
+ }
999
+ >
1000
+ );
1001
+
1002
+ const parsedVeScas = veScas.map(
1003
+ ({ keyId, lockedScaCoin, currentVeScaBalance, unlockAt }) => ({
1004
+ veScaKey: keyId,
1005
+ coinPrice: coinPrices.sca ?? 0,
1006
+ lockedScaInCoin: lockedScaCoin,
1007
+ lockedScaInUsd: lockedScaCoin * (coinPrices.sca ?? 0),
1008
+ currentVeScaBalance,
1009
+ remainingLockPeriodInDays:
1010
+ unlockAt - Date.now() > 0 ? (unlockAt - Date.now()) / 86400000 : 0,
1011
+ unlockAt,
1012
+ })
1013
+ );
1014
+
1015
+ return {
1016
+ totalSupplyValue: parsedLendings.reduce((acc, curr) => {
1017
+ acc += curr.suppliedValue;
1018
+ return acc;
1019
+ }, 0),
1020
+ ...parsedObligationAccounts.reduce(
1021
+ (acc, curr) => {
1022
+ acc.totalDebtValue += curr.totalDebtsInUsd;
1023
+ acc.totalCollateralValue += curr.totalCollateralInUsd;
1024
+ return acc;
1025
+ },
1026
+ {
1027
+ totalDebtValue: 0,
1028
+ totalCollateralValue: 0,
1029
+ } as {
1030
+ totalDebtValue: number;
1031
+ totalCollateralValue: number;
1032
+ }
1033
+ ),
1034
+ totalLockedScaValue: parsedVeScas.reduce((acc, curr) => {
1035
+ acc += curr.lockedScaInUsd;
1036
+ return acc;
1037
+ }, 0),
1038
+ lendings: parsedLendings,
1039
+ borrowings: parsedObligationAccounts,
1040
+ pendingRewards: {
1041
+ lendings: Object.entries(pendingLendingRewards).reduce(
1042
+ (acc, [key, value]) => {
1043
+ acc.push({
1044
+ ...value,
1045
+ coinName: key,
1046
+ pendingRewardInUsd: value.coinPrice * value.pendingRewardInCoin,
1047
+ });
1048
+ return acc;
1049
+ },
1050
+ [] as any
1051
+ ),
1052
+ borrowIncentives: Object.entries(pendingBorrowIncentiveRewards).reduce(
1053
+ (acc, [key, value]) => {
1054
+ acc.push({
1055
+ coinName: key,
1056
+ ...value,
1057
+ pendingRewardInUsd: value.coinPrice * value.pendingRewardInCoin,
1058
+ });
1059
+ return acc;
1060
+ },
1061
+ [] as any
1062
+ ),
1063
+ },
1064
+ veScas: parsedVeScas,
1065
+ };
1066
+ };
@@ -95,11 +95,11 @@ export const getSCoinAmount = async (
95
95
  ) => {
96
96
  const owner = ownerAddress || utils.suiKit.currentAddress();
97
97
  const sCoinType = utils.parseSCoinType(sCoinName);
98
- const amount = await utils.cache.queryGetCoinBalance({
98
+ const coinBalance = await utils.cache.queryGetCoinBalance({
99
99
  owner,
100
100
  coinType: sCoinType,
101
101
  });
102
- return BigNumber(amount).toNumber();
102
+ return BigNumber(coinBalance?.totalBalance ?? '0').toNumber();
103
103
  };
104
104
 
105
105
  const isSupportStakeCoins = (value: string): value is SupportSCoin => {
@@ -10,6 +10,7 @@ import type {
10
10
  } from '../models';
11
11
  import { ScallopCache } from 'src/models/scallopCache';
12
12
  import { AddressesInterface } from './address';
13
+ import { QueryClient, QueryClientConfig } from '@tanstack/query-core';
13
14
 
14
15
  export type ScallopClientFnReturnType<T extends boolean> = T extends true
15
16
  ? SuiTransactionBlockResponse
@@ -26,7 +27,9 @@ export type ScallopBaseInstanceParams = {
26
27
  suiKit?: SuiKit;
27
28
  };
28
29
 
29
- export type ScallopCacheInstanceParams = ScallopBaseInstanceParams;
30
+ export type ScallopCacheInstanceParams = ScallopBaseInstanceParams & {
31
+ queryClient?: QueryClient;
32
+ };
30
33
 
31
34
  export type ScallopAddressInstanceParams = ScallopBaseInstanceParams & {
32
35
  cache?: ScallopCache;
@@ -69,7 +72,8 @@ export type ScallopParams = {
69
72
  export type ScallopClientParams = ScallopParams &
70
73
  ScallopBuilderParams &
71
74
  ScallopQueryParams &
72
- ScallopUtilsParams;
75
+ ScallopUtilsParams &
76
+ ScallopCacheParams;
73
77
 
74
78
  export type ScallopBuilderParams = ScallopParams & {
75
79
  pythEndpoints?: string[];
@@ -81,3 +85,10 @@ export type ScallopQueryParams = ScallopParams & ScallopUtilsParams;
81
85
  export type ScallopUtilsParams = ScallopParams & {
82
86
  pythEndpoints?: string[];
83
87
  };
88
+
89
+ export type ScallopCacheParams = Omit<
90
+ ScallopParams,
91
+ 'addressId' | 'forceAddressesInterface'
92
+ > & {
93
+ cacheOptions?: QueryClientConfig;
94
+ };
@@ -1,7 +1,5 @@
1
1
  export * from './builder';
2
2
  export * from './query';
3
3
  export * from './util';
4
- export * from './tokenBucket';
5
4
  export * from './indexer';
6
5
  export * from './core';
7
- export * from './tokenBucket';
@@ -17,7 +17,9 @@ export async function callMethodWithIndexerFallback(
17
17
  try {
18
18
  return await method.apply(context, args);
19
19
  } catch (e: any) {
20
- console.warn(`Indexer requests failed: ${e}. Retrying without indexer..`);
20
+ console.warn(
21
+ `Indexer requests failed: ${e.message}. Retrying without indexer..`
22
+ );
21
23
  return await method.apply(context, [
22
24
  ...args.slice(0, -1),
23
25
  {
@@ -1,2 +0,0 @@
1
- export declare const DEFAULT_TOKENS_PER_INTERVAL = 10;
2
- export declare const DEFAULT_INTERVAL_IN_MS = 250;
@@ -1,11 +0,0 @@
1
- declare class TokenBucket {
2
- private tokensPerInterval;
3
- private interval;
4
- private tokens;
5
- private lastRefill;
6
- constructor(tokensPerInterval: number, intervalInMs: number);
7
- refill(): void;
8
- removeTokens(count: number): boolean;
9
- }
10
- declare const callWithRateLimit: <T>(tokenBucket: TokenBucket, fn: () => Promise<T>, retryDelayInMs?: number, maxRetries?: number, backoffFactor?: number) => Promise<T | null>;
11
- export { TokenBucket, callWithRateLimit };
@@ -1,2 +0,0 @@
1
- export const DEFAULT_TOKENS_PER_INTERVAL = 10;
2
- export const DEFAULT_INTERVAL_IN_MS = 250;
@@ -1,68 +0,0 @@
1
- import { DEFAULT_INTERVAL_IN_MS } from 'src/constants/tokenBucket';
2
-
3
- class TokenBucket {
4
- private tokensPerInterval: number;
5
- private interval: number;
6
- private tokens: number;
7
- private lastRefill: number;
8
-
9
- constructor(tokensPerInterval: number, intervalInMs: number) {
10
- this.tokensPerInterval = tokensPerInterval;
11
- this.interval = intervalInMs;
12
- this.tokens = tokensPerInterval;
13
- this.lastRefill = Date.now();
14
- }
15
-
16
- refill() {
17
- const now = Date.now();
18
- const elapsed = now - this.lastRefill;
19
-
20
- if (elapsed >= this.interval) {
21
- const tokensToAdd =
22
- Math.floor(elapsed / this.interval) * this.tokensPerInterval;
23
- this.tokens = Math.min(this.tokens + tokensToAdd, this.tokensPerInterval);
24
-
25
- // Update lastRefill to reflect the exact time of the last "refill"
26
- this.lastRefill += Math.floor(elapsed / this.interval) * this.interval;
27
- }
28
- }
29
-
30
- removeTokens(count: number) {
31
- this.refill();
32
- if (this.tokens >= count) {
33
- this.tokens -= count;
34
- return true;
35
- }
36
- return false;
37
- }
38
- }
39
-
40
- const callWithRateLimit = async <T>(
41
- tokenBucket: TokenBucket,
42
- fn: () => Promise<T>,
43
- retryDelayInMs = DEFAULT_INTERVAL_IN_MS,
44
- maxRetries = 15,
45
- backoffFactor = 1.25 // The factor by which to increase the delay
46
- ): Promise<T | null> => {
47
- let retries = 0;
48
-
49
- const tryRequest = async (): Promise<T | null> => {
50
- if (tokenBucket.removeTokens(1)) {
51
- const result = await fn();
52
- return result;
53
- } else if (retries < maxRetries) {
54
- retries++;
55
- const delay = retryDelayInMs * Math.pow(backoffFactor, retries);
56
- // console.error(`Rate limit exceeded, retrying in ${delay} ms`);
57
- await new Promise((resolve) => setTimeout(resolve, delay));
58
- return tryRequest();
59
- } else {
60
- console.error('Maximum retries reached');
61
- return null;
62
- }
63
- };
64
-
65
- return tryRequest();
66
- };
67
-
68
- export { TokenBucket, callWithRateLimit };