@scallop-io/sui-scallop-sdk 3.0.2 → 4.1.0

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 (283) hide show
  1. package/dist/Logger-Cg2iFdpH.d.ts +8 -0
  2. package/dist/builder.cjs +22 -0
  3. package/dist/builder.d.cts +28 -0
  4. package/dist/builder.d.ts +28 -0
  5. package/dist/builder.js +4 -0
  6. package/dist/chunk-33AK5RWM.js +15 -0
  7. package/dist/chunk-6UQ4NWEU.js +3 -0
  8. package/dist/chunk-DIC6P5HB.js +11 -0
  9. package/dist/chunk-S34VOEZN.js +3 -0
  10. package/dist/chunk-VTGQACWU.js +6 -0
  11. package/dist/client.cjs +21 -0
  12. package/dist/client.d.cts +401 -0
  13. package/dist/client.d.ts +401 -0
  14. package/dist/client.js +5 -0
  15. package/dist/errors.cjs +10 -0
  16. package/dist/errors.d.cts +32 -0
  17. package/dist/errors.d.ts +32 -0
  18. package/dist/errors.js +1 -0
  19. package/dist/index.cjs +51 -34
  20. package/dist/index.d.cts +130 -3145
  21. package/dist/index.d.ts +130 -3145
  22. package/dist/index.js +8 -12
  23. package/dist/logger.cjs +6 -0
  24. package/dist/logger.d.cts +7 -0
  25. package/dist/logger.d.ts +7 -0
  26. package/dist/logger.js +1 -0
  27. package/dist/query-DkitepDi.d.ts +3305 -0
  28. package/dist/query.cjs +19 -0
  29. package/dist/query.d.cts +9 -0
  30. package/dist/query.d.ts +9 -0
  31. package/dist/query.js +3 -0
  32. package/dist/types.cjs +2 -0
  33. package/dist/types.d.cts +90 -0
  34. package/dist/types.d.ts +90 -0
  35. package/dist/types.js +1 -0
  36. package/package.json +98 -56
  37. package/src/constants/api.ts +2 -0
  38. package/src/constants/coinType.ts +2 -0
  39. package/src/constants/common.ts +0 -7
  40. package/src/constants/index.ts +2 -0
  41. package/src/constants/queryKeys.ts +14 -7
  42. package/src/constants/testAddress.ts +1 -0
  43. package/src/datasources/api.ts +34 -0
  44. package/src/datasources/indexer.ts +8 -0
  45. package/src/datasources/onchain.ts +121 -0
  46. package/src/entries/builder.ts +5 -0
  47. package/src/entries/client.ts +3 -0
  48. package/src/entries/errors.ts +2 -0
  49. package/src/entries/index.ts +17 -0
  50. package/src/entries/logger.ts +2 -0
  51. package/src/entries/query.ts +6 -0
  52. package/src/entries/types.ts +2 -0
  53. package/src/errors/ScallopConfigError.ts +8 -0
  54. package/src/errors/ScallopError.ts +30 -0
  55. package/src/errors/ScallopIndexerError.ts +8 -0
  56. package/src/errors/ScallopParseError.ts +8 -0
  57. package/src/errors/ScallopRpcError.ts +8 -0
  58. package/src/errors/ScallopTransactionBuildError.ts +8 -0
  59. package/src/errors/index.ts +6 -0
  60. package/src/logger/Logger.ts +6 -0
  61. package/src/logger/consoleLogger.ts +13 -0
  62. package/src/logger/index.ts +3 -0
  63. package/src/logger/noopLogger.ts +8 -0
  64. package/src/mappers/index.ts +1 -0
  65. package/src/mappers/moveTypeMapper.ts +48 -0
  66. package/src/models/index.ts +8 -10
  67. package/src/models/interface.ts +9 -9
  68. package/src/models/scallop.ts +9 -16
  69. package/src/models/scallopAddress/const.ts +416 -0
  70. package/src/models/scallopAddress/index.ts +188 -0
  71. package/src/models/scallopAddress/types.ts +212 -0
  72. package/src/models/{scallopBuilder.ts → scallopBuilder/index.ts} +65 -29
  73. package/src/models/scallopBuilder/types.ts +37 -0
  74. package/src/models/{scallopClient.ts → scallopClient/index.ts} +157 -598
  75. package/src/models/scallopClient/types.ts +25 -0
  76. package/src/models/scallopConstants/config/AddressConfigSource.ts +13 -0
  77. package/src/models/scallopConstants/config/ConfigValidator.ts +70 -0
  78. package/src/models/scallopConstants/config/PoolAddressConfigSource.ts +12 -0
  79. package/src/models/scallopConstants/config/ScallopConfig.ts +80 -0
  80. package/src/models/scallopConstants/config/ScallopConfigSnapshot.ts +46 -0
  81. package/src/models/scallopConstants/config/WhitelistConfigSource.ts +16 -0
  82. package/src/models/scallopConstants/config/index.ts +6 -0
  83. package/src/models/scallopConstants/const.ts +19 -0
  84. package/src/models/scallopConstants/constantsSource.ts +98 -0
  85. package/src/models/scallopConstants/deriveConstants.ts +150 -0
  86. package/src/models/scallopConstants/index.ts +257 -0
  87. package/src/models/scallopConstants/loadConstantsState.ts +135 -0
  88. package/src/models/scallopConstants/types.ts +77 -0
  89. package/src/models/scallopConstants/utils.ts +69 -0
  90. package/src/models/scallopQuery/index.ts +1279 -0
  91. package/src/models/scallopQuery/types.ts +9 -0
  92. package/src/models/scallopQuery/utils.ts +13 -0
  93. package/src/models/scallopUtils/index.ts +505 -0
  94. package/src/models/scallopUtils/types.ts +30 -0
  95. package/src/models/transactionExecutor.ts +122 -0
  96. package/src/repositories/addressApi/helpers.ts +14 -0
  97. package/src/repositories/addressApi/index.ts +24 -0
  98. package/src/repositories/addressApi/schema.ts +0 -0
  99. package/src/repositories/addressApi/types.ts +177 -0
  100. package/src/repositories/base.ts +38 -0
  101. package/src/repositories/borrowIncentive/bcs.ts +11 -0
  102. package/src/repositories/borrowIncentive/helpers.ts +396 -0
  103. package/src/repositories/borrowIncentive/index.ts +52 -0
  104. package/src/{types/query/borrowIncentive.ts → repositories/borrowIncentive/types.ts} +69 -23
  105. package/src/repositories/borrowIncentive/utils.ts +234 -0
  106. package/src/repositories/cache.ts +4 -0
  107. package/src/repositories/coinBalance/helpers.ts +394 -0
  108. package/src/repositories/coinBalance/index.ts +69 -0
  109. package/src/repositories/coinBalance/types.ts +33 -0
  110. package/src/repositories/flashloan/const.ts +3 -0
  111. package/src/repositories/flashloan/helpers.ts +107 -0
  112. package/src/repositories/flashloan/index.ts +34 -0
  113. package/src/repositories/flashloan/types.ts +16 -0
  114. package/src/repositories/isolatedAssets/bcs.ts +4 -0
  115. package/src/repositories/isolatedAssets/const.ts +2 -0
  116. package/src/repositories/isolatedAssets/helpers.ts +91 -0
  117. package/src/repositories/isolatedAssets/index.ts +42 -0
  118. package/src/repositories/isolatedAssets/types.ts +35 -0
  119. package/src/repositories/loyaltyProgram/bcs.ts +7 -0
  120. package/src/repositories/loyaltyProgram/helpers.ts +117 -0
  121. package/src/repositories/loyaltyProgram/index.ts +28 -0
  122. package/src/repositories/loyaltyProgram/schema.ts +18 -0
  123. package/src/repositories/loyaltyProgram/types.ts +37 -0
  124. package/src/repositories/market/const.ts +6 -0
  125. package/src/repositories/market/helpers.ts +740 -0
  126. package/src/repositories/market/index.ts +137 -0
  127. package/src/repositories/market/mapper.ts +30 -0
  128. package/src/repositories/market/types.ts +443 -0
  129. package/src/repositories/market/utils.ts +285 -0
  130. package/src/repositories/obligation/helpers.ts +228 -0
  131. package/src/repositories/obligation/index.ts +51 -0
  132. package/src/repositories/obligation/types.ts +71 -0
  133. package/src/repositories/obligation/utils.ts +57 -0
  134. package/src/repositories/poolAddresses/const.ts +5 -0
  135. package/src/repositories/poolAddresses/helpers.ts +422 -0
  136. package/src/repositories/poolAddresses/index.ts +56 -0
  137. package/src/repositories/poolAddresses/schema.ts +34 -0
  138. package/src/repositories/poolAddresses/types.ts +69 -0
  139. package/src/repositories/price/const.ts +1 -0
  140. package/src/repositories/price/helpers.ts +237 -0
  141. package/src/repositories/price/index.ts +91 -0
  142. package/src/repositories/price/schema.ts +24 -0
  143. package/src/repositories/price/types.ts +56 -0
  144. package/src/repositories/price/utils.ts +21 -0
  145. package/src/repositories/referral/bcs.ts +3 -0
  146. package/src/repositories/referral/helper.ts +24 -0
  147. package/src/repositories/referral/index.ts +31 -0
  148. package/src/repositories/referral/types.ts +33 -0
  149. package/src/repositories/spool/const.ts +1 -0
  150. package/src/repositories/spool/helpers.ts +471 -0
  151. package/src/repositories/spool/index.ts +135 -0
  152. package/src/{types/query/spool.ts → repositories/spool/types.ts} +67 -2
  153. package/src/repositories/spool/utils.ts +215 -0
  154. package/src/repositories/types.ts +39 -0
  155. package/src/repositories/utils.ts +137 -0
  156. package/src/repositories/veSca/bcs.ts +6 -0
  157. package/src/repositories/veSca/helpers.ts +341 -0
  158. package/src/repositories/veSca/index.ts +57 -0
  159. package/src/repositories/veSca/types.ts +79 -0
  160. package/src/repositories/veScaLoyaltyProgram/bcs.ts +3 -0
  161. package/src/repositories/veScaLoyaltyProgram/helpers.ts +135 -0
  162. package/src/repositories/veScaLoyaltyProgram/index.ts +28 -0
  163. package/src/repositories/veScaLoyaltyProgram/schema.ts +15 -0
  164. package/src/repositories/veScaLoyaltyProgram/types.ts +47 -0
  165. package/src/repositories/wiring/datasources.ts +42 -0
  166. package/src/repositories/wiring/metadata.ts +292 -0
  167. package/src/repositories/wiring/registry.ts +194 -0
  168. package/src/repositories/wiring/source.ts +63 -0
  169. package/src/repositories/xOracle/bcs.ts +7 -0
  170. package/src/repositories/xOracle/const.ts +1 -0
  171. package/src/repositories/xOracle/helpers.ts +304 -0
  172. package/src/repositories/xOracle/index.ts +45 -0
  173. package/src/repositories/xOracle/types.ts +96 -0
  174. package/src/repositories/xOracle/utils.ts +3 -0
  175. package/src/services/client/BorrowService.ts +229 -0
  176. package/src/services/client/CollateralService.ts +76 -0
  177. package/src/services/client/LendingService.ts +75 -0
  178. package/src/services/client/ReferralService.ts +71 -0
  179. package/src/services/client/SpoolService.ts +170 -0
  180. package/src/services/client/VeScaService.ts +142 -0
  181. package/src/services/client/types.ts +44 -0
  182. package/src/services/index.ts +8 -0
  183. package/src/services/query/portfolioCalculations.ts +1171 -0
  184. package/src/txBuilders/borrowIncentive/index.ts +80 -0
  185. package/src/txBuilders/borrowIncentive/moveCalls.ts +124 -0
  186. package/src/txBuilders/borrowIncentive/quick.ts +207 -0
  187. package/src/txBuilders/context.ts +62 -0
  188. package/src/txBuilders/core/index.ts +95 -0
  189. package/src/txBuilders/core/moveCalls.ts +241 -0
  190. package/src/txBuilders/core/quick.ts +306 -0
  191. package/src/{builders → txBuilders}/index.ts +35 -9
  192. package/src/txBuilders/loyaltyProgram/index.ts +78 -0
  193. package/src/txBuilders/loyaltyProgram/moveCalls.ts +44 -0
  194. package/src/txBuilders/loyaltyProgram/quick.ts +58 -0
  195. package/src/txBuilders/manifest.ts +160 -0
  196. package/src/txBuilders/modules.ts +70 -0
  197. package/src/{builders → txBuilders}/oracles/index.ts +3 -2
  198. package/src/{builders → txBuilders}/oracles/pyth.ts +4 -3
  199. package/src/txBuilders/referral/index.ts +72 -0
  200. package/src/txBuilders/referral/moveCalls.ts +93 -0
  201. package/src/txBuilders/referral/quick.ts +57 -0
  202. package/src/txBuilders/sCoin/index.ts +72 -0
  203. package/src/txBuilders/sCoin/moveCalls.ts +36 -0
  204. package/src/txBuilders/sCoin/quick.ts +34 -0
  205. package/src/txBuilders/spool/index.ts +82 -0
  206. package/src/txBuilders/spool/moveCalls.ts +79 -0
  207. package/src/{builders/spoolBuilder.ts → txBuilders/spool/quick.ts} +22 -162
  208. package/src/txBuilders/utils.ts +36 -0
  209. package/src/txBuilders/verify.ts +45 -0
  210. package/src/txBuilders/vesca/index.ts +75 -0
  211. package/src/txBuilders/vesca/moveCalls.ts +147 -0
  212. package/src/{builders/vescaBuilder.ts → txBuilders/vesca/quick.ts} +85 -274
  213. package/src/types/address.ts +4 -178
  214. package/src/types/builder/borrowIncentive.ts +4 -3
  215. package/src/types/builder/core.ts +6 -33
  216. package/src/types/builder/index.ts +5 -1
  217. package/src/types/builder/loyaltyProgram.ts +4 -3
  218. package/src/types/builder/modules.ts +49 -0
  219. package/src/types/builder/referral.ts +5 -4
  220. package/src/types/builder/sCoin.ts +20 -2
  221. package/src/types/builder/spool.ts +22 -2
  222. package/src/types/builder/vesca.ts +4 -12
  223. package/src/types/constant/index.ts +3 -1
  224. package/src/types/constant/queryKeys.ts +15 -8
  225. package/src/types/index.ts +5 -6
  226. package/src/types/internal/dto.ts +46 -0
  227. package/src/types/internal/index.ts +2 -0
  228. package/src/types/internal/move.ts +13 -0
  229. package/src/types/public/index.ts +21 -0
  230. package/src/types/query/core.ts +0 -317
  231. package/src/types/query/index.ts +3 -4
  232. package/src/types/query/portfolio.ts +2 -15
  233. package/src/types/repositories/borrowIncentive.ts +12 -0
  234. package/src/types/repositories/index.ts +9 -0
  235. package/src/types/repositories/loyaltyProgram.ts +2 -0
  236. package/src/types/repositories/market.ts +13 -0
  237. package/src/types/repositories/spool.ts +21 -0
  238. package/src/types/repositories/veSca.ts +5 -0
  239. package/src/types/repositories/veScaLoyaltyProgram.ts +2 -0
  240. package/src/types/sui.ts +0 -1
  241. package/src/utils/array.ts +7 -0
  242. package/src/utils/cache.ts +17 -0
  243. package/src/utils/object.ts +30 -12
  244. package/src/utils/query.ts +9 -190
  245. package/src/utils/vesca.ts +0 -8
  246. package/src/builders/borrowIncentiveBuilder.ts +0 -372
  247. package/src/builders/coreBuilder.ts +0 -636
  248. package/src/builders/loyaltyProgramBuilder.ts +0 -142
  249. package/src/builders/referralBuilder.ts +0 -192
  250. package/src/builders/sCoinBuilder.ts +0 -124
  251. package/src/index.ts +0 -13
  252. package/src/models/scallopAddress.ts +0 -841
  253. package/src/models/scallopAxios.ts +0 -185
  254. package/src/models/scallopConstants.ts +0 -380
  255. package/src/models/scallopIndexer.ts +0 -226
  256. package/src/models/scallopQuery.ts +0 -974
  257. package/src/models/scallopQueryClient.ts +0 -29
  258. package/src/models/scallopSuiKit.ts +0 -480
  259. package/src/models/scallopUtils.ts +0 -734
  260. package/src/queries/borrowIncentiveQuery.ts +0 -361
  261. package/src/queries/borrowLimitQuery.ts +0 -33
  262. package/src/queries/coreQuery.ts +0 -1173
  263. package/src/queries/flashloanFeeQuery.ts +0 -92
  264. package/src/queries/index.ts +0 -15
  265. package/src/queries/isolatedAssetQuery.ts +0 -103
  266. package/src/queries/loyaltyProgramQuery.ts +0 -178
  267. package/src/queries/ownerQuery.ts +0 -32
  268. package/src/queries/poolAddressesQuery.ts +0 -340
  269. package/src/queries/portfolioQuery.ts +0 -1149
  270. package/src/queries/priceQuery.ts +0 -37
  271. package/src/queries/referralQuery.ts +0 -42
  272. package/src/queries/sCoinQuery.ts +0 -195
  273. package/src/queries/spoolQuery.ts +0 -542
  274. package/src/queries/supplyLimitQuery.ts +0 -32
  275. package/src/queries/switchboardQuery.ts +0 -65
  276. package/src/queries/vescaQuery.ts +0 -312
  277. package/src/queries/xOracleQuery.ts +0 -149
  278. package/src/types/constant/common.ts +0 -55
  279. package/src/types/query/loyaltyProgram.ts +0 -12
  280. package/src/types/query/vesca.ts +0 -27
  281. package/src/utils/index.ts +0 -7
  282. package/src/utils/indexer.ts +0 -47
  283. /package/src/{models → datasources}/rateLimiter.ts +0 -0
@@ -0,0 +1,304 @@
1
+ import { SuiClientTypes } from '@mysten/sui/client';
2
+ import {
3
+ SupportedOracle,
4
+ XOracleAssetOraclesContext,
5
+ XOracleOnDemandAggContext,
6
+ XOraclePriceUpdatePolicyContext,
7
+ XOracleSwitchboardRegistryContext,
8
+ XOracleUpdatePolicyRulesContext,
9
+ } from './types.js';
10
+ import { queryKeys } from 'src/constants/queryKeys.js';
11
+ import { bcs } from '@mysten/sui/bcs';
12
+ import { prepend0x } from './utils.js';
13
+ import { PricePolicyRulesVecSet } from './bcs.js';
14
+ import { getDynamicFieldOrNull, logError } from '../utils.js';
15
+ import { encodeDynamicFieldNameForV2 } from 'src/utils/dynamicField.js';
16
+ import { ScallopParseError, ScallopRpcError } from 'src/errors/index.js';
17
+
18
+ const SWITCHBOARD_REGISTRY_SCAN_THRESHOLD = 3;
19
+
20
+ const queryUpdatePolicyRules = async (
21
+ ctx: XOracleUpdatePolicyRulesContext,
22
+ vecSetId: string
23
+ ): Promise<Record<string, SupportedOracle[]>> => {
24
+ const { onchain, fetchWithCache, metadata } = ctx;
25
+ const { addresses, parseCoinNameFromType } = metadata;
26
+ const limit = 50;
27
+
28
+ const ruleTypeNameToOracleType: Record<string, SupportedOracle> = {
29
+ [`${addresses.pyth.object}::rule::Rule`]: 'pyth',
30
+ [`${addresses.supra.object}::rule::Rule`]: 'supra',
31
+ [`${addresses.switchboard.object}::rule::Rule`]: 'switchboard',
32
+ };
33
+
34
+ let nextCursor = null;
35
+ let nextPage = true;
36
+
37
+ const results: Record<string, SupportedOracle[]> = {};
38
+
39
+ do {
40
+ const fetchOptions: SuiClientTypes.ListDynamicFieldsOptions = {
41
+ parentId: vecSetId,
42
+ cursor: nextCursor,
43
+ limit,
44
+ //@ts-ignore - Supported on grpc implementation
45
+ include: {
46
+ value: true,
47
+ },
48
+ };
49
+ const { dynamicFields, cursor, hasNextPage } = await fetchWithCache({
50
+ queryKey: queryKeys.rpc.getDynamicFields({
51
+ ...fetchOptions,
52
+ node: onchain.url,
53
+ }),
54
+ queryFn: () => onchain.client.listDynamicFields(fetchOptions),
55
+ });
56
+
57
+ nextCursor = cursor;
58
+ nextPage = hasNextPage;
59
+
60
+ // Process the dynamic fields
61
+ dynamicFields.forEach((field) => {
62
+ // Get coin type
63
+ const coinType = prepend0x(bcs.string().parse(field.name.bcs));
64
+ const coinName = parseCoinNameFromType(coinType);
65
+ // @ts-ignore - value is supported on grpc implementation
66
+ const parsedValue = PricePolicyRulesVecSet.parse(field.value.bcs);
67
+
68
+ if (!results[coinName]) {
69
+ results[coinName] = [];
70
+ }
71
+
72
+ // Iterate through parsedValue
73
+ parsedValue.contents.forEach(({ name }) => {
74
+ const oracle = ruleTypeNameToOracleType[prepend0x(name)];
75
+ if (oracle) {
76
+ results[coinName].push(oracle);
77
+ }
78
+ });
79
+ });
80
+ } while (nextPage);
81
+
82
+ return results;
83
+ };
84
+
85
+ export const getAssetOraclesFromOnChain = async (
86
+ ctx: XOracleAssetOraclesContext
87
+ ) => {
88
+ const {
89
+ metadata: { addresses, whitelist },
90
+ } = ctx;
91
+ const [primary, secondary] = await Promise.all([
92
+ queryUpdatePolicyRules(
93
+ ctx,
94
+ addresses.oracles.primaryPriceUpdatePolicyVecsetId
95
+ ),
96
+ queryUpdatePolicyRules(
97
+ ctx,
98
+ addresses.oracles.secondaryPriceUpdatePolicyVecsetId
99
+ ),
100
+ ]);
101
+
102
+ return [...whitelist.lending.values()].reduce(
103
+ (acc, assetName) => {
104
+ acc[assetName] = {
105
+ primary: primary[assetName] ?? [],
106
+ secondary: secondary[assetName] ?? [],
107
+ };
108
+ return acc;
109
+ },
110
+ {} as Record<
111
+ string,
112
+ { primary: SupportedOracle[]; secondary: SupportedOracle[] }
113
+ >
114
+ );
115
+ };
116
+
117
+ /**
118
+ * Primary/secondary price-update-policy dynamic fields.
119
+ *
120
+ * NOTE: the return shape changed from the legacy `getPriceUpdatePolicies`
121
+ * (which leaked `SuiObjectResponse`). This returns the new-gen client's
122
+ * dynamic-field result (or `null` when the policy rules key isn't present).
123
+ */
124
+ export const getPriceUpdatePoliciesFromOnChain = async (
125
+ ctx: XOraclePriceUpdatePolicyContext
126
+ ) => {
127
+ const { addresses } = ctx.metadata;
128
+ const keyType = `${addresses.xOracleObject}::price_update_policy::PriceUpdatePolicyRulesKey`;
129
+ const nameBcs = bcs
130
+ .struct('TypeName', { dummy_field: bcs.bool() })
131
+ .serialize({ dummy_field: false })
132
+ .toBytes();
133
+
134
+ const [primary, secondary] = await Promise.all([
135
+ getDynamicFieldOrNull(ctx, {
136
+ parentId: addresses.oracles.primaryPriceUpdatePolicyObject,
137
+ name: { type: keyType, bcs: nameBcs },
138
+ }),
139
+ getDynamicFieldOrNull(ctx, {
140
+ parentId: addresses.oracles.secondaryPriceUpdatePolicyObject,
141
+ name: { type: keyType, bcs: nameBcs },
142
+ }),
143
+ ]);
144
+
145
+ return { primary, secondary };
146
+ };
147
+
148
+ /**
149
+ * Switchboard on-demand aggregator object ids for the given coins, aligned to
150
+ * the input order. Uses the address-API registration when present, else looks
151
+ * the coin up in the on-chain switchboard registry table.
152
+ */
153
+ export const getOnDemandAggObjectIdsFromOnChain = async (
154
+ ctx: XOracleOnDemandAggContext,
155
+ coinNames: string[]
156
+ ): Promise<string[]> => {
157
+ const { addresses, parseCoinType, getSwitchboardAggAddress } = ctx.metadata;
158
+ const registryTableId = addresses.oracles.switchboardRegistryTableId;
159
+
160
+ const missingAgg: Array<{ idx: number; coinName: string }> = [];
161
+ const registeredAggs: Array<string | null> = coinNames.map(
162
+ (coinName, idx) => {
163
+ const registered = getSwitchboardAggAddress(coinName);
164
+ if (registered) return registered;
165
+ missingAgg.push({ idx, coinName });
166
+ return null;
167
+ }
168
+ );
169
+
170
+ if (missingAgg.length === 0) return registeredAggs as string[];
171
+
172
+ if (missingAgg.length > SWITCHBOARD_REGISTRY_SCAN_THRESHOLD) {
173
+ const missingCoinTypes = new Map<
174
+ string,
175
+ { idx: number; coinName: string }
176
+ >();
177
+ for (const missing of missingAgg) {
178
+ const coinType = parseCoinType(missing.coinName);
179
+ if (!coinType) {
180
+ throw logError(
181
+ ctx.logger,
182
+ new ScallopParseError(`Invalid coin name: ${missing.coinName}`, {
183
+ context: { coinName: missing.coinName },
184
+ })
185
+ );
186
+ }
187
+ missingCoinTypes.set(coinType.slice(2), missing);
188
+ }
189
+
190
+ const scannedAggs = await querySwitchboardRegistryAggs(
191
+ ctx,
192
+ registryTableId,
193
+ missingCoinTypes
194
+ );
195
+ for (const [coinTypeKey, { idx }] of missingCoinTypes) {
196
+ const agg = scannedAggs[coinTypeKey];
197
+ if (!agg) {
198
+ throw logError(
199
+ ctx.logger,
200
+ new ScallopRpcError(
201
+ `No on-demand aggregator found for 0x${coinTypeKey}`,
202
+ { context: { coinTypeKey } }
203
+ )
204
+ );
205
+ }
206
+ registeredAggs[idx] = agg;
207
+ }
208
+
209
+ return registeredAggs as string[];
210
+ }
211
+
212
+ await Promise.all(
213
+ missingAgg.map(async ({ idx, coinName }) => {
214
+ const coinType = parseCoinType(coinName);
215
+ if (!coinType) {
216
+ throw logError(
217
+ ctx.logger,
218
+ new ScallopParseError(`Invalid coin name: ${coinName}`, {
219
+ context: { coinName },
220
+ })
221
+ );
222
+ }
223
+ const result = await getDynamicFieldOrNull(ctx, {
224
+ parentId: registryTableId,
225
+ name: encodeDynamicFieldNameForV2({
226
+ type: '0x1::type_name::TypeName',
227
+ value: { name: coinType.slice(2) },
228
+ }),
229
+ });
230
+ if (!result) {
231
+ throw logError(
232
+ ctx.logger,
233
+ new ScallopRpcError(`No on-demand aggregator found for ${coinType}`, {
234
+ context: { coinType },
235
+ })
236
+ );
237
+ }
238
+ // The registry value is the aggregator object id (an address).
239
+ // UNVERIFIED: confirm the bcs shape against a live registry entry.
240
+ registeredAggs[idx] = bcs.Address.parse(result.dynamicField.value.bcs);
241
+ })
242
+ );
243
+
244
+ return registeredAggs as string[];
245
+ };
246
+
247
+ const parseRegistryCoinTypeKey = (
248
+ field: SuiClientTypes.DynamicFieldEntry
249
+ ): string | undefined => {
250
+ try {
251
+ return bcs.struct('TypeName', { name: bcs.string() }).parse(field.name.bcs)
252
+ .name;
253
+ } catch {
254
+ try {
255
+ return bcs.string().parse(field.name.bcs);
256
+ } catch {
257
+ return undefined;
258
+ }
259
+ }
260
+ };
261
+
262
+ const querySwitchboardRegistryAggs = async (
263
+ ctx: XOracleSwitchboardRegistryContext,
264
+ registryTableId: string,
265
+ missingCoinTypes: ReadonlyMap<string, { idx: number; coinName: string }>
266
+ ): Promise<Record<string, string>> => {
267
+ const { onchain, fetchWithCache } = ctx;
268
+ const result: Record<string, string> = {};
269
+ let cursor: string | null | undefined = null;
270
+ let hasNextPage = false;
271
+
272
+ do {
273
+ const options: SuiClientTypes.ListDynamicFieldsOptions = {
274
+ parentId: registryTableId,
275
+ cursor,
276
+ limit: 50,
277
+ // @ts-ignore - Supported on grpc implementation
278
+ include: { value: true },
279
+ };
280
+ const resp = await fetchWithCache({
281
+ queryKey: queryKeys.rpc.getDynamicFields({
282
+ ...options,
283
+ node: onchain.url,
284
+ }),
285
+ queryFn: () => onchain.client.listDynamicFields(options),
286
+ });
287
+
288
+ for (const field of resp.dynamicFields) {
289
+ const coinTypeKey = parseRegistryCoinTypeKey(field);
290
+ if (!coinTypeKey || !missingCoinTypes.has(coinTypeKey)) continue;
291
+ // @ts-ignore - value is supported on grpc implementation
292
+ const valueBcs = field.value?.bcs;
293
+ if (valueBcs) {
294
+ result[coinTypeKey] = bcs.Address.parse(valueBcs);
295
+ }
296
+ }
297
+
298
+ cursor = resp.cursor;
299
+ hasNextPage =
300
+ resp.hasNextPage && Object.keys(result).length < missingCoinTypes.size;
301
+ } while (hasNextPage);
302
+
303
+ return result;
304
+ };
@@ -0,0 +1,45 @@
1
+ import { BaseRepository } from '../base.js';
2
+ import { OnChainDataSource } from 'src/datasources/onchain.js';
3
+ import {
4
+ getAssetOraclesFromOnChain,
5
+ getOnDemandAggObjectIdsFromOnChain,
6
+ getPriceUpdatePoliciesFromOnChain,
7
+ } from './helpers.js';
8
+ import {
9
+ XOracleMetadata,
10
+ XOracleRepoParams,
11
+ XOracleRepoContext,
12
+ } from './types.js';
13
+
14
+ export class XOracleRepository extends BaseRepository<
15
+ XOracleRepoContext,
16
+ XOracleMetadata
17
+ > {
18
+ private readonly onchain: OnChainDataSource;
19
+
20
+ constructor({ onchain, ...params }: XOracleRepoParams) {
21
+ super(params);
22
+ this.onchain = onchain;
23
+ }
24
+
25
+ get context() {
26
+ return { ...this.baseContext, onchain: this.onchain };
27
+ }
28
+
29
+ /**
30
+ * Return supported primary and secondary oracles for supported assets
31
+ */
32
+ getAssetOracles() {
33
+ return getAssetOraclesFromOnChain(this.context);
34
+ }
35
+
36
+ /** Primary/secondary price-update-policy dynamic fields. */
37
+ getPriceUpdatePolicies() {
38
+ return getPriceUpdatePoliciesFromOnChain(this.context);
39
+ }
40
+
41
+ /** Switchboard on-demand aggregator object ids for the given coins. */
42
+ getOnDemandAggObjectIds(coinNames: string[]) {
43
+ return getOnDemandAggObjectIdsFromOnChain(this.context, coinNames);
44
+ }
45
+ }
@@ -0,0 +1,96 @@
1
+ import { BaseContext, BaseRepoParams } from '../types.js';
2
+ import { OnChainDataSource } from 'src/datasources/onchain.js';
3
+ import { SUPPORTED_ORACLES } from './const.js';
4
+
5
+ export type SupportedOracle = (typeof SUPPORTED_ORACLES)[number];
6
+
7
+ type SupportedOracleAddresses = Record<SupportedOracle, { object: string }>;
8
+ type XOracleAddresses = {
9
+ /** `core.packages.xOracle.object` — used to build the policy-rules key type. */
10
+ xOracleObject: string;
11
+ oracles: {
12
+ primaryPriceUpdatePolicyVecsetId: string;
13
+ secondaryPriceUpdatePolicyVecsetId: string;
14
+ primaryPriceUpdatePolicyObject: string;
15
+ secondaryPriceUpdatePolicyObject: string;
16
+ switchboardRegistryTableId: string;
17
+ };
18
+ };
19
+
20
+ export type XOracleMetadata = {
21
+ addresses: SupportedOracleAddresses & XOracleAddresses;
22
+ whitelist: {
23
+ lending: ReadonlySet<string>;
24
+ };
25
+ parseCoinNameFromType: (type: string) => string;
26
+ parseCoinType: (coinName: string) => string;
27
+ /** Pre-registered switchboard aggregator id for a coin, if the address API
28
+ * carries one (`core.coins.<coin>.oracle.switchboard`). */
29
+ getSwitchboardAggAddress: (coinName: string) => string | undefined;
30
+ };
31
+
32
+ export type XOracleRepoContext = BaseContext & {
33
+ onchain: OnChainDataSource;
34
+ metadata: XOracleMetadata;
35
+ };
36
+
37
+ export type XOracleRepoParams = BaseRepoParams & {
38
+ onchain: OnChainDataSource;
39
+ metadata: XOracleMetadata;
40
+ };
41
+
42
+ /**
43
+ * Reads update-policy rule VecSets per coin: paginated on-chain dynamic-field
44
+ * scan + coin-name parsing. Needs the oracle rule package addresses.
45
+ */
46
+ export type XOracleUpdatePolicyRulesContext = Pick<
47
+ XOracleRepoContext,
48
+ 'onchain' | 'fetchWithCache' | 'logger'
49
+ > & {
50
+ metadata: Pick<XOracleMetadata, 'addresses' | 'parseCoinNameFromType'>;
51
+ };
52
+
53
+ /**
54
+ * Assembles primary/secondary asset oracles across the lending whitelist.
55
+ * Reads the rule VecSet ids + lending whitelist; delegates per-vecset scans to
56
+ * `queryUpdatePolicyRules` (hence the rule-scan slice on top).
57
+ */
58
+ export type XOracleAssetOraclesContext = XOracleUpdatePolicyRulesContext & {
59
+ metadata: Pick<
60
+ XOracleMetadata,
61
+ 'addresses' | 'parseCoinNameFromType' | 'whitelist'
62
+ >;
63
+ };
64
+
65
+ /**
66
+ * Reads the price-update-policy dynamic fields. Needs only the policy object
67
+ * ids from `addresses`; the dynamic-field reads go through
68
+ * `getDynamicFieldOrNull` (hence the `onchain`/`fetchWithCache` slice).
69
+ */
70
+ export type XOraclePriceUpdatePolicyContext = Pick<
71
+ XOracleRepoContext,
72
+ 'onchain' | 'fetchWithCache'
73
+ > & {
74
+ metadata: Pick<XOracleMetadata, 'addresses'>;
75
+ };
76
+
77
+ /**
78
+ * Resolves switchboard on-demand aggregator ids for coins. Needs the registry
79
+ * table id + coin-type/aggregator parse helpers, plus the on-chain reads for
80
+ * the registry scan / dynamic-field lookups.
81
+ */
82
+ export type XOracleOnDemandAggContext = Pick<
83
+ XOracleRepoContext,
84
+ 'onchain' | 'fetchWithCache' | 'logger'
85
+ > & {
86
+ metadata: Pick<
87
+ XOracleMetadata,
88
+ 'addresses' | 'parseCoinType' | 'getSwitchboardAggAddress'
89
+ >;
90
+ };
91
+
92
+ /** Paginated scan of the switchboard registry table. On-chain reads only. */
93
+ export type XOracleSwitchboardRegistryContext = Pick<
94
+ XOracleRepoContext,
95
+ 'onchain' | 'fetchWithCache'
96
+ >;
@@ -0,0 +1,3 @@
1
+ export const prepend0x = (str: string) => {
2
+ return str.startsWith('0x') ? str : `0x${str}`;
3
+ };
@@ -0,0 +1,229 @@
1
+ import type { TransactionResult } from '@mysten/sui/transactions';
2
+ import type { ClientServiceContext, ClientTxResult } from './types.js';
3
+ import { ScallopTransactionBuildError } from 'src/errors/index.js';
4
+
5
+ /**
6
+ * Application service for borrow-side lifecycle operations:
7
+ * - obligation creation
8
+ * - borrow / repay
9
+ * - supply + auto-stake convenience
10
+ * - borrow-incentive stake / unstake / claim
11
+ *
12
+ * Each method preserves the public ScallopClient signature one-to-one.
13
+ */
14
+ export class BorrowService {
15
+ constructor(private readonly ctx: ClientServiceContext) {}
16
+
17
+ async openObligation<S extends boolean>(
18
+ sign: S = true as S
19
+ ): Promise<ClientTxResult<S>> {
20
+ const txBlock = this.ctx.builder.createTxBlock();
21
+ txBlock.openObligationEntry();
22
+ if (sign) {
23
+ return (await this.ctx.executor.signAndSendTxn(
24
+ txBlock
25
+ )) as ClientTxResult<S>;
26
+ }
27
+ return txBlock.txBlock as ClientTxResult<S>;
28
+ }
29
+
30
+ async borrow<S extends boolean>(
31
+ poolCoinName: string,
32
+ amount: number,
33
+ sign: S = true as S,
34
+ obligationId: string,
35
+ obligationKey: string,
36
+ walletAddress?: string,
37
+ isSponsoredTx?: boolean
38
+ ): Promise<ClientTxResult<S>> {
39
+ const txBlock = this.ctx.builder.createTxBlock();
40
+ const sender = walletAddress ?? this.ctx.walletAddress;
41
+ txBlock.setSender(sender);
42
+
43
+ const availableStake =
44
+ this.ctx.constants.whitelist.lending.has(poolCoinName);
45
+ if (sign && availableStake) {
46
+ await txBlock.unstakeObligationQuick(obligationId, obligationKey);
47
+ }
48
+ const coin = await txBlock.borrowQuick(
49
+ amount,
50
+ poolCoinName,
51
+ obligationId,
52
+ obligationKey,
53
+ { isSponsoredTx }
54
+ );
55
+ txBlock.transferObjects([coin], sender);
56
+ if (sign && availableStake) {
57
+ await txBlock.stakeObligationWithVeScaQuick(obligationId, obligationKey);
58
+ }
59
+
60
+ if (sign) {
61
+ return (await this.ctx.executor.signAndSendTxn(
62
+ txBlock
63
+ )) as ClientTxResult<S>;
64
+ }
65
+ return txBlock.txBlock as ClientTxResult<S>;
66
+ }
67
+
68
+ async repay<S extends boolean>(
69
+ poolCoinName: string,
70
+ amount: number,
71
+ sign: S = true as S,
72
+ obligationId: string,
73
+ obligationKey: string,
74
+ walletAddress?: string,
75
+ isSponsoredTx?: boolean
76
+ ): Promise<ClientTxResult<S>> {
77
+ const txBlock = this.ctx.builder.createTxBlock();
78
+ const sender = walletAddress ?? this.ctx.walletAddress;
79
+ txBlock.setSender(sender);
80
+
81
+ const availableStake =
82
+ this.ctx.constants.whitelist.lending.has(poolCoinName);
83
+ if (sign && availableStake) {
84
+ await txBlock.unstakeObligationQuick(obligationId, obligationKey);
85
+ }
86
+ await txBlock.repayQuick(amount, poolCoinName, obligationId, isSponsoredTx);
87
+ if (sign && availableStake) {
88
+ await txBlock.stakeObligationWithVeScaQuick(obligationId, obligationKey);
89
+ }
90
+
91
+ if (sign) {
92
+ return (await this.ctx.executor.signAndSendTxn(
93
+ txBlock
94
+ )) as ClientTxResult<S>;
95
+ }
96
+ return txBlock.txBlock as ClientTxResult<S>;
97
+ }
98
+
99
+ async supplyAndStake<S extends boolean>(
100
+ stakeCoinName: string,
101
+ amount: number,
102
+ sign: S = true as S,
103
+ stakeAccountId?: string,
104
+ walletAddress?: string
105
+ ): Promise<ClientTxResult<S>> {
106
+ const txBlock = this.ctx.builder.createTxBlock();
107
+ const sender = walletAddress ?? this.ctx.walletAddress;
108
+ txBlock.setSender(sender);
109
+
110
+ const stakeMarketCoinName =
111
+ this.ctx.utils.parseMarketCoinName<string>(stakeCoinName);
112
+ const stakeAccounts =
113
+ await this.ctx.query.getStakeAccounts(stakeMarketCoinName);
114
+ const targetStakeAccount = stakeAccountId ?? stakeAccounts[0]?.id;
115
+
116
+ const marketCoin = await txBlock.supplyQuick(amount, stakeCoinName, false);
117
+ if (targetStakeAccount) {
118
+ await txBlock.stakeQuick(
119
+ marketCoin,
120
+ stakeMarketCoinName,
121
+ targetStakeAccount
122
+ );
123
+ } else {
124
+ const account = txBlock.createStakeAccount(stakeMarketCoinName);
125
+ await txBlock.stakeQuick(marketCoin, stakeMarketCoinName, account);
126
+ txBlock.transferObjects([account], sender);
127
+ }
128
+
129
+ if (sign) {
130
+ return (await this.ctx.executor.signAndSendTxn(
131
+ txBlock
132
+ )) as ClientTxResult<S>;
133
+ }
134
+ return txBlock.txBlock as ClientTxResult<S>;
135
+ }
136
+
137
+ async stakeObligation<S extends boolean>(
138
+ obligationId: string,
139
+ obligationKeyId: string,
140
+ sign: S = true as S,
141
+ walletAddress?: string
142
+ ): Promise<ClientTxResult<S>> {
143
+ const txBlock = this.ctx.builder.createTxBlock();
144
+ txBlock.setSender(walletAddress ?? this.ctx.walletAddress);
145
+ await txBlock.stakeObligationWithVeScaQuick(obligationId, obligationKeyId);
146
+
147
+ if (sign) {
148
+ return (await this.ctx.executor.signAndSendTxn(
149
+ txBlock
150
+ )) as ClientTxResult<S>;
151
+ }
152
+ return txBlock.txBlock as ClientTxResult<S>;
153
+ }
154
+
155
+ async unstakeObligation<S extends boolean>(
156
+ obligationId: string,
157
+ obligationKeyId: string,
158
+ sign: S = true as S,
159
+ walletAddress?: string
160
+ ): Promise<ClientTxResult<S>> {
161
+ const txBlock = this.ctx.builder.createTxBlock();
162
+ txBlock.setSender(walletAddress ?? this.ctx.walletAddress);
163
+ await txBlock.unstakeObligationQuick(obligationId, obligationKeyId);
164
+
165
+ if (sign) {
166
+ return (await this.ctx.executor.signAndSendTxn(
167
+ txBlock
168
+ )) as ClientTxResult<S>;
169
+ }
170
+ return txBlock.txBlock as ClientTxResult<S>;
171
+ }
172
+
173
+ async claimBorrowIncentive<S extends boolean>(
174
+ obligationId: string,
175
+ obligationKeyId: string,
176
+ sign: S = true as S,
177
+ walletAddress?: string
178
+ ): Promise<ClientTxResult<S>> {
179
+ const txBlock = this.ctx.builder.createTxBlock();
180
+ const sender = walletAddress ?? this.ctx.walletAddress;
181
+ txBlock.setSender(sender);
182
+
183
+ const rewardCoinsCollection: Record<string, TransactionResult[]> = {};
184
+ const obligationAccount =
185
+ await this.ctx.query.getObligationAccount(obligationId);
186
+ if (!obligationAccount) {
187
+ throw new ScallopTransactionBuildError('Obligation not found', {
188
+ context: { obligationId },
189
+ });
190
+ }
191
+ const rewardCoinNames = Object.values(obligationAccount.borrowIncentives)
192
+ .filter((t): t is NonNullable<typeof t> => !!t)
193
+ .flatMap(({ rewards }) =>
194
+ rewards.filter(({ availableClaimAmount }) => availableClaimAmount > 0)
195
+ )
196
+ .flatMap(({ coinName }) => coinName);
197
+
198
+ for (const rewardCoinName of rewardCoinNames) {
199
+ const rewardCoin = await txBlock.claimBorrowIncentiveQuick(
200
+ rewardCoinName,
201
+ obligationId,
202
+ obligationKeyId
203
+ );
204
+ if (!rewardCoinsCollection[rewardCoinName]) {
205
+ rewardCoinsCollection[rewardCoinName] = [rewardCoin];
206
+ } else {
207
+ rewardCoinsCollection[rewardCoinName].push(rewardCoin);
208
+ }
209
+ }
210
+
211
+ txBlock.transferObjects(
212
+ Object.values(rewardCoinsCollection).map((rewardCoins) => {
213
+ const mergeDest = rewardCoins[0];
214
+ if (rewardCoins.length > 1) {
215
+ txBlock.mergeCoins(mergeDest, rewardCoins.slice(1));
216
+ }
217
+ return mergeDest;
218
+ }),
219
+ sender
220
+ );
221
+
222
+ if (sign) {
223
+ return (await this.ctx.executor.signAndSendTxn(
224
+ txBlock
225
+ )) as ClientTxResult<S>;
226
+ }
227
+ return txBlock.txBlock as ClientTxResult<S>;
228
+ }
229
+ }