@zofai/zo-sdk 0.1.92

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 (275) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.gitattributes +4 -0
  3. package/.prettierrc.js +9 -0
  4. package/README.md +28 -0
  5. package/dist/abstract/BaseAPI.cjs +206 -0
  6. package/dist/abstract/BaseAPI.cjs.map +1 -0
  7. package/dist/abstract/BaseAPI.d.cts +172 -0
  8. package/dist/abstract/BaseAPI.d.cts.map +1 -0
  9. package/dist/abstract/BaseAPI.d.mts +172 -0
  10. package/dist/abstract/BaseAPI.d.mts.map +1 -0
  11. package/dist/abstract/BaseAPI.mjs +202 -0
  12. package/dist/abstract/BaseAPI.mjs.map +1 -0
  13. package/dist/abstract/BaseDataAPI.cjs +140 -0
  14. package/dist/abstract/BaseDataAPI.cjs.map +1 -0
  15. package/dist/abstract/BaseDataAPI.d.cts +89 -0
  16. package/dist/abstract/BaseDataAPI.d.cts.map +1 -0
  17. package/dist/abstract/BaseDataAPI.d.mts +89 -0
  18. package/dist/abstract/BaseDataAPI.d.mts.map +1 -0
  19. package/dist/abstract/BaseDataAPI.mjs +136 -0
  20. package/dist/abstract/BaseDataAPI.mjs.map +1 -0
  21. package/dist/abstract/index.cjs +12 -0
  22. package/dist/abstract/index.cjs.map +1 -0
  23. package/dist/abstract/index.d.cts +7 -0
  24. package/dist/abstract/index.d.cts.map +1 -0
  25. package/dist/abstract/index.d.mts +7 -0
  26. package/dist/abstract/index.d.mts.map +1 -0
  27. package/dist/abstract/index.mjs +7 -0
  28. package/dist/abstract/index.mjs.map +1 -0
  29. package/dist/api.cjs +779 -0
  30. package/dist/api.cjs.map +1 -0
  31. package/dist/api.d.cts +75 -0
  32. package/dist/api.d.cts.map +1 -0
  33. package/dist/api.d.mts +75 -0
  34. package/dist/api.d.mts.map +1 -0
  35. package/dist/api.mjs +775 -0
  36. package/dist/api.mjs.map +1 -0
  37. package/dist/bcs.cjs +42 -0
  38. package/dist/bcs.cjs.map +1 -0
  39. package/dist/bcs.d.cts +91 -0
  40. package/dist/bcs.d.cts.map +1 -0
  41. package/dist/bcs.d.mts +91 -0
  42. package/dist/bcs.d.mts.map +1 -0
  43. package/dist/bcs.mjs +39 -0
  44. package/dist/bcs.mjs.map +1 -0
  45. package/dist/consts/deployments-shared-mainnet.json +50 -0
  46. package/dist/consts/deployments-shared-testnet.json +45 -0
  47. package/dist/consts/deployments-slp-mainnet.json +600 -0
  48. package/dist/consts/deployments-slp-testnet.json +87 -0
  49. package/dist/consts/deployments-usdz-mainnet.json +494 -0
  50. package/dist/consts/deployments-usdz-testnet.json +98 -0
  51. package/dist/consts/deployments-zbtcvc-mainnet.json +180 -0
  52. package/dist/consts/deployments-zlp-mainnet.json +791 -0
  53. package/dist/consts/deployments-zlp-testnet.json +76 -0
  54. package/dist/consts/index.cjs +200 -0
  55. package/dist/consts/index.cjs.map +1 -0
  56. package/dist/consts/index.d.cts +157 -0
  57. package/dist/consts/index.d.cts.map +1 -0
  58. package/dist/consts/index.d.mts +157 -0
  59. package/dist/consts/index.d.mts.map +1 -0
  60. package/dist/consts/index.mjs +189 -0
  61. package/dist/consts/index.mjs.map +1 -0
  62. package/dist/consts/price_id_to_object_id.mainnet.json +56 -0
  63. package/dist/consts/price_id_to_object_id.testnet.json +17 -0
  64. package/dist/data.cjs +919 -0
  65. package/dist/data.cjs.map +1 -0
  66. package/dist/data.d.cts +235 -0
  67. package/dist/data.d.cts.map +1 -0
  68. package/dist/data.d.mts +235 -0
  69. package/dist/data.d.mts.map +1 -0
  70. package/dist/data.mjs +915 -0
  71. package/dist/data.mjs.map +1 -0
  72. package/dist/factory/SDKFactory.cjs +228 -0
  73. package/dist/factory/SDKFactory.cjs.map +1 -0
  74. package/dist/factory/SDKFactory.d.cts +84 -0
  75. package/dist/factory/SDKFactory.d.cts.map +1 -0
  76. package/dist/factory/SDKFactory.d.mts +84 -0
  77. package/dist/factory/SDKFactory.d.mts.map +1 -0
  78. package/dist/factory/SDKFactory.mjs +222 -0
  79. package/dist/factory/SDKFactory.mjs.map +1 -0
  80. package/dist/implementations/SLPAPI.cjs +1794 -0
  81. package/dist/implementations/SLPAPI.cjs.map +1 -0
  82. package/dist/implementations/SLPAPI.d.cts +183 -0
  83. package/dist/implementations/SLPAPI.d.cts.map +1 -0
  84. package/dist/implementations/SLPAPI.d.mts +183 -0
  85. package/dist/implementations/SLPAPI.d.mts.map +1 -0
  86. package/dist/implementations/SLPAPI.mjs +1790 -0
  87. package/dist/implementations/SLPAPI.mjs.map +1 -0
  88. package/dist/implementations/SLPDataAPI.cjs +1384 -0
  89. package/dist/implementations/SLPDataAPI.cjs.map +1 -0
  90. package/dist/implementations/SLPDataAPI.d.cts +158 -0
  91. package/dist/implementations/SLPDataAPI.d.cts.map +1 -0
  92. package/dist/implementations/SLPDataAPI.d.mts +158 -0
  93. package/dist/implementations/SLPDataAPI.d.mts.map +1 -0
  94. package/dist/implementations/SLPDataAPI.mjs +1380 -0
  95. package/dist/implementations/SLPDataAPI.mjs.map +1 -0
  96. package/dist/implementations/USDZAPI.cjs +1676 -0
  97. package/dist/implementations/USDZAPI.cjs.map +1 -0
  98. package/dist/implementations/USDZAPI.d.cts +180 -0
  99. package/dist/implementations/USDZAPI.d.cts.map +1 -0
  100. package/dist/implementations/USDZAPI.d.mts +180 -0
  101. package/dist/implementations/USDZAPI.d.mts.map +1 -0
  102. package/dist/implementations/USDZAPI.mjs +1672 -0
  103. package/dist/implementations/USDZAPI.mjs.map +1 -0
  104. package/dist/implementations/USDZDataAPI.cjs +1209 -0
  105. package/dist/implementations/USDZDataAPI.cjs.map +1 -0
  106. package/dist/implementations/USDZDataAPI.d.cts +191 -0
  107. package/dist/implementations/USDZDataAPI.d.cts.map +1 -0
  108. package/dist/implementations/USDZDataAPI.d.mts +191 -0
  109. package/dist/implementations/USDZDataAPI.d.mts.map +1 -0
  110. package/dist/implementations/USDZDataAPI.mjs +1205 -0
  111. package/dist/implementations/USDZDataAPI.mjs.map +1 -0
  112. package/dist/implementations/ZBTCVCAPI.cjs +906 -0
  113. package/dist/implementations/ZBTCVCAPI.cjs.map +1 -0
  114. package/dist/implementations/ZBTCVCAPI.d.cts +107 -0
  115. package/dist/implementations/ZBTCVCAPI.d.cts.map +1 -0
  116. package/dist/implementations/ZBTCVCAPI.d.mts +107 -0
  117. package/dist/implementations/ZBTCVCAPI.d.mts.map +1 -0
  118. package/dist/implementations/ZBTCVCAPI.mjs +902 -0
  119. package/dist/implementations/ZBTCVCAPI.mjs.map +1 -0
  120. package/dist/implementations/ZBTCVCDataAPI.cjs +829 -0
  121. package/dist/implementations/ZBTCVCDataAPI.cjs.map +1 -0
  122. package/dist/implementations/ZBTCVCDataAPI.d.cts +94 -0
  123. package/dist/implementations/ZBTCVCDataAPI.d.cts.map +1 -0
  124. package/dist/implementations/ZBTCVCDataAPI.d.mts +94 -0
  125. package/dist/implementations/ZBTCVCDataAPI.d.mts.map +1 -0
  126. package/dist/implementations/ZBTCVCDataAPI.mjs +825 -0
  127. package/dist/implementations/ZBTCVCDataAPI.mjs.map +1 -0
  128. package/dist/implementations/ZLPAPI.cjs +1948 -0
  129. package/dist/implementations/ZLPAPI.cjs.map +1 -0
  130. package/dist/implementations/ZLPAPI.d.cts +192 -0
  131. package/dist/implementations/ZLPAPI.d.cts.map +1 -0
  132. package/dist/implementations/ZLPAPI.d.mts +192 -0
  133. package/dist/implementations/ZLPAPI.d.mts.map +1 -0
  134. package/dist/implementations/ZLPAPI.mjs +1944 -0
  135. package/dist/implementations/ZLPAPI.mjs.map +1 -0
  136. package/dist/implementations/ZLPDataAPI.cjs +1267 -0
  137. package/dist/implementations/ZLPDataAPI.cjs.map +1 -0
  138. package/dist/implementations/ZLPDataAPI.d.cts +193 -0
  139. package/dist/implementations/ZLPDataAPI.d.cts.map +1 -0
  140. package/dist/implementations/ZLPDataAPI.d.mts +193 -0
  141. package/dist/implementations/ZLPDataAPI.d.mts.map +1 -0
  142. package/dist/implementations/ZLPDataAPI.mjs +1263 -0
  143. package/dist/implementations/ZLPDataAPI.mjs.map +1 -0
  144. package/dist/implementations/index.cjs +26 -0
  145. package/dist/implementations/index.cjs.map +1 -0
  146. package/dist/implementations/index.d.cts +13 -0
  147. package/dist/implementations/index.d.cts.map +1 -0
  148. package/dist/implementations/index.d.mts +13 -0
  149. package/dist/implementations/index.d.mts.map +1 -0
  150. package/dist/implementations/index.mjs +15 -0
  151. package/dist/implementations/index.mjs.map +1 -0
  152. package/dist/index.cjs +69 -0
  153. package/dist/index.cjs.map +1 -0
  154. package/dist/index.d.cts +51 -0
  155. package/dist/index.d.cts.map +1 -0
  156. package/dist/index.d.mts +51 -0
  157. package/dist/index.d.mts.map +1 -0
  158. package/dist/index.mjs +51 -0
  159. package/dist/index.mjs.map +1 -0
  160. package/dist/interfaces/base.cjs +7 -0
  161. package/dist/interfaces/base.cjs.map +1 -0
  162. package/dist/interfaces/base.d.cts +346 -0
  163. package/dist/interfaces/base.d.cts.map +1 -0
  164. package/dist/interfaces/base.d.mts +346 -0
  165. package/dist/interfaces/base.d.mts.map +1 -0
  166. package/dist/interfaces/base.mjs +6 -0
  167. package/dist/interfaces/base.mjs.map +1 -0
  168. package/dist/interfaces/index.cjs +31 -0
  169. package/dist/interfaces/index.cjs.map +1 -0
  170. package/dist/interfaces/index.d.cts +15 -0
  171. package/dist/interfaces/index.d.cts.map +1 -0
  172. package/dist/interfaces/index.d.mts +15 -0
  173. package/dist/interfaces/index.d.mts.map +1 -0
  174. package/dist/interfaces/index.mjs +15 -0
  175. package/dist/interfaces/index.mjs.map +1 -0
  176. package/dist/interfaces/slp.cjs +7 -0
  177. package/dist/interfaces/slp.cjs.map +1 -0
  178. package/dist/interfaces/slp.d.cts +179 -0
  179. package/dist/interfaces/slp.d.cts.map +1 -0
  180. package/dist/interfaces/slp.d.mts +179 -0
  181. package/dist/interfaces/slp.d.mts.map +1 -0
  182. package/dist/interfaces/slp.mjs +6 -0
  183. package/dist/interfaces/slp.mjs.map +1 -0
  184. package/dist/interfaces/usdz.cjs +7 -0
  185. package/dist/interfaces/usdz.cjs.map +1 -0
  186. package/dist/interfaces/usdz.d.cts +104 -0
  187. package/dist/interfaces/usdz.d.cts.map +1 -0
  188. package/dist/interfaces/usdz.d.mts +104 -0
  189. package/dist/interfaces/usdz.d.mts.map +1 -0
  190. package/dist/interfaces/usdz.mjs +6 -0
  191. package/dist/interfaces/usdz.mjs.map +1 -0
  192. package/dist/interfaces/zbtcvc.cjs +7 -0
  193. package/dist/interfaces/zbtcvc.cjs.map +1 -0
  194. package/dist/interfaces/zbtcvc.d.cts +64 -0
  195. package/dist/interfaces/zbtcvc.d.cts.map +1 -0
  196. package/dist/interfaces/zbtcvc.d.mts +64 -0
  197. package/dist/interfaces/zbtcvc.d.mts.map +1 -0
  198. package/dist/interfaces/zbtcvc.mjs +6 -0
  199. package/dist/interfaces/zbtcvc.mjs.map +1 -0
  200. package/dist/interfaces/zlp.cjs +7 -0
  201. package/dist/interfaces/zlp.cjs.map +1 -0
  202. package/dist/interfaces/zlp.d.cts +114 -0
  203. package/dist/interfaces/zlp.d.cts.map +1 -0
  204. package/dist/interfaces/zlp.d.mts +114 -0
  205. package/dist/interfaces/zlp.d.mts.map +1 -0
  206. package/dist/interfaces/zlp.mjs +6 -0
  207. package/dist/interfaces/zlp.mjs.map +1 -0
  208. package/dist/oracle.cjs +118 -0
  209. package/dist/oracle.cjs.map +1 -0
  210. package/dist/oracle.d.cts +25 -0
  211. package/dist/oracle.d.cts.map +1 -0
  212. package/dist/oracle.d.mts +25 -0
  213. package/dist/oracle.d.mts.map +1 -0
  214. package/dist/oracle.mjs +114 -0
  215. package/dist/oracle.mjs.map +1 -0
  216. package/dist/utils.cjs +129 -0
  217. package/dist/utils.cjs.map +1 -0
  218. package/dist/utils.d.cts +44 -0
  219. package/dist/utils.d.cts.map +1 -0
  220. package/dist/utils.d.mts +44 -0
  221. package/dist/utils.d.mts.map +1 -0
  222. package/dist/utils.mjs +115 -0
  223. package/dist/utils.mjs.map +1 -0
  224. package/docs/SUMMARY.md +10 -0
  225. package/docs/api-reference.md +32 -0
  226. package/docs/architecture.md +14 -0
  227. package/docs/common-operations.md +52 -0
  228. package/docs/error-handling.md +17 -0
  229. package/docs/getting-started.md +60 -0
  230. package/docs/introduction.md +15 -0
  231. package/docs/lp-specific-features.md +96 -0
  232. package/docs/type-safety.md +29 -0
  233. package/eslint.config.mjs +18 -0
  234. package/package.json +42 -0
  235. package/src/abstract/BaseAPI.ts +575 -0
  236. package/src/abstract/BaseDataAPI.ts +207 -0
  237. package/src/abstract/index.ts +7 -0
  238. package/src/api.ts +1100 -0
  239. package/src/bcs.ts +45 -0
  240. package/src/consts/deployments-shared-mainnet.json +50 -0
  241. package/src/consts/deployments-shared-testnet.json +45 -0
  242. package/src/consts/deployments-slp-mainnet.json +600 -0
  243. package/src/consts/deployments-slp-testnet.json +87 -0
  244. package/src/consts/deployments-usdz-mainnet.json +494 -0
  245. package/src/consts/deployments-usdz-testnet.json +98 -0
  246. package/src/consts/deployments-zbtcvc-mainnet.json +180 -0
  247. package/src/consts/deployments-zlp-mainnet.json +791 -0
  248. package/src/consts/deployments-zlp-testnet.json +76 -0
  249. package/src/consts/index.ts +345 -0
  250. package/src/consts/price_id_to_object_id.mainnet.json +56 -0
  251. package/src/consts/price_id_to_object_id.testnet.json +17 -0
  252. package/src/data.ts +1279 -0
  253. package/src/factory/SDKFactory.ts +340 -0
  254. package/src/implementations/SLPAPI.ts +2722 -0
  255. package/src/implementations/SLPDataAPI.ts +1839 -0
  256. package/src/implementations/USDZAPI.ts +2488 -0
  257. package/src/implementations/USDZDataAPI.ts +1548 -0
  258. package/src/implementations/ZBTCVCAPI.ts +1337 -0
  259. package/src/implementations/ZBTCVCDataAPI.ts +993 -0
  260. package/src/implementations/ZLPAPI.ts +2888 -0
  261. package/src/implementations/ZLPDataAPI.ts +1603 -0
  262. package/src/implementations/index.ts +16 -0
  263. package/src/index.ts +58 -0
  264. package/src/interfaces/base.ts +838 -0
  265. package/src/interfaces/index.ts +50 -0
  266. package/src/interfaces/slp.ts +268 -0
  267. package/src/interfaces/usdz.ts +181 -0
  268. package/src/interfaces/zbtcvc.ts +116 -0
  269. package/src/interfaces/zlp.ts +244 -0
  270. package/src/oracle.ts +153 -0
  271. package/src/utils.ts +168 -0
  272. package/tests/api.test.ts +219 -0
  273. package/tests/data.test.ts +156 -0
  274. package/tests/oracle.test.ts +33 -0
  275. package/tsconfig.json +22 -0
@@ -0,0 +1,1380 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ /* eslint-disable @stylistic/indent-binary-ops */
3
+ /* eslint-disable @stylistic/indent */
4
+ /**
5
+ * SLP DataAPI implementation
6
+ * Implements SLP-specific data access methods for Sudo SDK
7
+ */
8
+ import { Transaction } from "@mysten/sui/transactions";
9
+ import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui/utils";
10
+ import { BaseDataAPI } from "../abstract/index.mjs";
11
+ import { Rate, SymbolsValuation, VaultsValuation } from "../bcs.mjs";
12
+ import { LPToken, SLP_TOKEN_DECIMALS } from "../consts/index.mjs";
13
+ import { joinSymbol, parseSymbolKey, parseValue, suiSymbolToSymbol } from "../utils.mjs";
14
+ let aprResponse = {};
15
+ const SECONDS_PER_EIGHT_HOUR = 8 * 60 * 60; // 28800 seconds
16
+ export class SLPDataAPI extends BaseDataAPI {
17
+ constructor(network, provider, apiEndpoint, connectionURL) {
18
+ super(network, provider, apiEndpoint, connectionURL, LPToken.SLP);
19
+ }
20
+ static calculateVaultReservingFee(vaultInfo, reservingFeeModel, currentTime) {
21
+ const timeDelta = currentTime - vaultInfo.lastUpdate;
22
+ const periods = Math.floor(timeDelta / SECONDS_PER_EIGHT_HOUR);
23
+ return vaultInfo.unrealisedReservingFeeAmount
24
+ + (vaultInfo.reservedAmount * reservingFeeModel.multiplier * periods) / 1e18;
25
+ }
26
+ static calculateSymbolFundingFee(symbol, model, price, lpSupplyAmount, timestamp, oiModel, pairedOpeningSize) {
27
+ const accFundingRate = SLPDataAPI.calcAccFundingFeeRate(symbol, model, price, lpSupplyAmount, timestamp, symbol.long, oiModel, pairedOpeningSize);
28
+ return symbol.unrealisedFundingFeeValue + (accFundingRate - symbol.accFundingRate) * symbol.openingSize;
29
+ }
30
+ async getRebaseFeeModel() {
31
+ this.validateCache();
32
+ if (this.rebaseFeeModelCache) {
33
+ return this.rebaseFeeModelCache;
34
+ }
35
+ const rawData = await this.provider.getObject({
36
+ id: this.consts.sudoCore.rebaseFeeModel,
37
+ options: {
38
+ showContent: true,
39
+ },
40
+ });
41
+ const model = SLPDataAPI.parseRebaseFeeModel(rawData);
42
+ const exponent = await SLPDataAPI.getRebaseFeeExponent(this.provider, this.consts.sudoCore.rebaseFeeModel, this.consts.sudoCore.upgradedPackage);
43
+ return { ...model, exponent };
44
+ }
45
+ /**
46
+ * Creates vaults valuation for SLP using Sudo SDK approach
47
+ */
48
+ valuateVaults(tx) {
49
+ if (!this.consts.sudoCore) {
50
+ throw new Error('Sudo Core configuration not found. Make sure you are using LPToken.SLP');
51
+ }
52
+ const vaultsValuation = tx.moveCall({
53
+ target: `${this.consts.sudoCore.upgradedPackage}::market::create_vaults_valuation`,
54
+ typeArguments: [`${this.consts.sudoCore.package}::slp::SLP`],
55
+ arguments: [
56
+ tx.object(SUI_CLOCK_OBJECT_ID),
57
+ tx.object(this.consts.sudoCore.market),
58
+ ],
59
+ });
60
+ for (const key of Object.keys(this.consts.sudoCore.vaults)) {
61
+ const vault = this.consts.sudoCore.vaults[key];
62
+ tx.moveCall({
63
+ target: `${this.consts.sudoCore.upgradedPackage}::market::valuate_vault_v1_1`,
64
+ typeArguments: [
65
+ `${this.consts.sudoCore.package}::slp::SLP`,
66
+ this.consts.coins[key].module,
67
+ ],
68
+ arguments: [
69
+ tx.object(this.consts.sudoCore.market),
70
+ tx.object(vault.reservingFeeModel),
71
+ tx.object(this.consts.pythFeeder.feeder[key]),
72
+ vaultsValuation,
73
+ ],
74
+ });
75
+ }
76
+ return vaultsValuation;
77
+ }
78
+ /**
79
+ * Creates symbols valuation for SLP using Sudo SDK approach
80
+ */
81
+ valuateSymbols(tx) {
82
+ if (!this.consts.sudoCore) {
83
+ throw new Error('Sudo Core configuration not found. Make sure you are using LPToken.SLP');
84
+ }
85
+ const symbolsValuation = tx.moveCall({
86
+ target: `${this.consts.sudoCore.upgradedPackage}::market::create_symbols_valuation`,
87
+ typeArguments: [`${this.consts.sudoCore.package}::slp::SLP`],
88
+ arguments: [
89
+ tx.object(SUI_CLOCK_OBJECT_ID),
90
+ tx.object(this.consts.sudoCore.market),
91
+ ],
92
+ });
93
+ for (const key of Object.keys(this.consts.sudoCore.symbols)) {
94
+ const [direction, token] = parseSymbolKey(key);
95
+ const symbol = this.consts.sudoCore.symbols[key];
96
+ tx.moveCall({
97
+ target: `${this.consts.sudoCore.upgradedPackage}::market::valuate_symbol_v1_1`,
98
+ typeArguments: [
99
+ `${this.consts.sudoCore.package}::slp::SLP`,
100
+ this.consts.coins[token].module,
101
+ `${this.consts.sudoCore.package}::market::${direction.toUpperCase()}`,
102
+ ],
103
+ arguments: [
104
+ tx.object(this.consts.sudoCore.market),
105
+ tx.object(symbol.fundingFeeModel),
106
+ tx.object(this.consts.pythFeeder.feeder[token]),
107
+ symbolsValuation,
108
+ ],
109
+ });
110
+ }
111
+ return symbolsValuation;
112
+ }
113
+ /**
114
+ * Creates both vaults and symbols valuation for SLP
115
+ */
116
+ valuate(tx) {
117
+ const vaultsValuation = this.valuateVaults(tx);
118
+ const symbolsValuation = this.valuateSymbols(tx);
119
+ return { vaultsValuation, symbolsValuation };
120
+ }
121
+ /**
122
+ * Valuates the SLP market using Sudo SDK's approach
123
+ */
124
+ async valuateMarket() {
125
+ const marketInfo = await this.getMarketInfo();
126
+ let slpPrice = 0;
127
+ let value = 0;
128
+ const vaultPromises = Object.keys(this.consts.sudoCore.vaults).map(async (vault) => {
129
+ const vaultInfo = await this.getVaultInfo(vault);
130
+ const reservingFeeDelta = SLPDataAPI.calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000);
131
+ const totalVaultAmount = reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount;
132
+ const oraclePrice = (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked();
133
+ const { decimals } = this.consts.coins[vault];
134
+ const vaultValue = totalVaultAmount * oraclePrice / (10 ** decimals);
135
+ return vaultValue;
136
+ });
137
+ const symbolPromises = Object.keys(this.consts.sudoCore.symbols).map(async (symbol) => {
138
+ const [direction, tokenId] = parseSymbolKey(symbol);
139
+ const symbolInfo = await this.getSymbolInfo(tokenId, direction === 'long');
140
+ const price = (await this.getOraclePrice(tokenId)).getPriceUnchecked().getPriceAsNumberUnchecked();
141
+ const deltaSize = SLPDataAPI.calcDeltaSize(symbolInfo, price, direction === 'long');
142
+ const oiState = await this.getSymbolOiFundingState(tokenId);
143
+ const pairedInfo = await this.getSymbolInfo(tokenId, direction !== 'long');
144
+ const fundingFeeDelta = SLPDataAPI.calculateSymbolFundingFee(symbolInfo, symbolInfo.fundingFeeModel, price, marketInfo.lpSupplyWithDecimals, Date.now() / 1000, oiState && oiState.enabled ? oiState.model : undefined, pairedInfo.openingSize);
145
+ const symbolValue = fundingFeeDelta + deltaSize;
146
+ return symbolValue;
147
+ });
148
+ const [vaultValues, symbolValues] = await Promise.all([Promise.all(vaultPromises), Promise.all(symbolPromises)]);
149
+ const totalVaultValue = vaultValues.reduce((acc, curr) => acc + curr, 0);
150
+ const totalSymbolValue = symbolValues.reduce((acc, curr) => acc + curr, 0);
151
+ value = totalVaultValue + totalSymbolValue;
152
+ slpPrice = value / marketInfo.lpSupplyWithDecimals;
153
+ return {
154
+ marketCap: value,
155
+ price: slpPrice,
156
+ supply: marketInfo.lpSupplyWithDecimals,
157
+ apr: Number(marketInfo.apr),
158
+ };
159
+ }
160
+ /**
161
+ * Valuates market using simulation (Sudo SDK specific)
162
+ */
163
+ async simValuate(sender) {
164
+ const tx = await this.initOracleTxb(Object.keys(this.consts.pythFeeder.feeder));
165
+ this.valuate(tx);
166
+ const res = await this.provider.devInspectTransactionBlock({
167
+ transactionBlock: tx,
168
+ sender,
169
+ });
170
+ const symbolsValuationOffset = Object.keys(this.consts.sudoCore.symbols).length + 1;
171
+ const vaultsValuation = VaultsValuation.parse(new Uint8Array(res.results[(res.results?.length || 0) - symbolsValuationOffset - 1].mutableReferenceOutputs[1][1]));
172
+ const symbolsValuation = SymbolsValuation.parse(new Uint8Array(res.results[(res.results?.length || 0) - 1]
173
+ .mutableReferenceOutputs[1][1]));
174
+ const result = Number(BigInt(vaultsValuation.value)
175
+ + BigInt(symbolsValuation.value.value)
176
+ * BigInt(symbolsValuation.value.is_positive ? 1 : -1)) / 1e18;
177
+ return result;
178
+ }
179
+ /**
180
+ * Valuates only vaults (Sudo SDK specific)
181
+ */
182
+ async simValuateVaults(sender) {
183
+ const tx = await this.initOracleTxb(Object.keys(this.consts.sudoCore.vaults));
184
+ this.valuateVaults(tx);
185
+ const res = await this.provider.devInspectTransactionBlock({
186
+ transactionBlock: tx,
187
+ sender,
188
+ });
189
+ const vaultsValuation = VaultsValuation.parse(new Uint8Array(res.results[(res.results?.length || 0) - 1]
190
+ .mutableReferenceOutputs[1][1]));
191
+ const result = Number(BigInt(vaultsValuation.value)) / 1e18;
192
+ return result;
193
+ }
194
+ /**
195
+ * Valuates market with vaults only
196
+ */
197
+ async valuateMarketWithVaultsOnly() {
198
+ if (!this.consts.sudoCore) {
199
+ throw new Error('Sudo Core configuration not found');
200
+ }
201
+ const marketInfo = await this.getMarketInfo();
202
+ const value = await this.simValuateVaults(this.consts.sudoCore.adminCap);
203
+ const slpPrice = value / marketInfo.lpSupplyWithDecimals;
204
+ return {
205
+ marketCap: value,
206
+ price: slpPrice,
207
+ supply: marketInfo.lpSupplyWithDecimals,
208
+ apr: Number(marketInfo.apr),
209
+ };
210
+ }
211
+ /**
212
+ * Gets SLP market information
213
+ */
214
+ async getMarketInfo() {
215
+ this.validateCache();
216
+ if (this.marketInfoCache) {
217
+ return this.marketInfoCache;
218
+ }
219
+ const rawData = await this.provider.getObject({
220
+ id: this.consts.sudoCore.market,
221
+ options: {
222
+ showContent: true,
223
+ },
224
+ });
225
+ const apr = await this.getCumulativeApr();
226
+ return {
227
+ ...SLPDataAPI.parseMarketInfo(rawData),
228
+ apr,
229
+ };
230
+ }
231
+ /**
232
+ * Gets SLP vault information
233
+ */
234
+ async getVaultInfo(vaultToken) {
235
+ this.validateCache();
236
+ if (this.vaultInfoCache[vaultToken]) {
237
+ return this.vaultInfoCache[vaultToken];
238
+ }
239
+ const rawData = await this.provider.getDynamicFieldObject({
240
+ parentId: this.consts.sudoCore.vaultsParent,
241
+ name: {
242
+ type: `${this.consts.sudoCore.package}::market::VaultName<${this.consts.coins[vaultToken].module}>`,
243
+ value: { dummy_field: false },
244
+ },
245
+ });
246
+ return await this.parseVaultInfo(rawData);
247
+ }
248
+ /**
249
+ * Gets SLP symbol information
250
+ */
251
+ async getSymbolInfo(indexToken, long) {
252
+ this.validateCache();
253
+ const symbol = joinSymbol(long ? 'long' : 'short', indexToken);
254
+ if (this.symbolInfoCache[symbol]) {
255
+ return this.symbolInfoCache[symbol];
256
+ }
257
+ const rawData = await this.provider.getDynamicFieldObject({
258
+ parentId: this.consts.sudoCore.symbolsParent,
259
+ name: {
260
+ type: `${this.consts.sudoCore.package}::market::SymbolName<${this.consts.coins[indexToken].module}, ${this.consts.sudoCore.package}::market::${long ? 'LONG' : 'SHORT'}>`,
261
+ value: { dummy_field: false },
262
+ },
263
+ });
264
+ return await this.parseSymbolInfo(rawData, long);
265
+ }
266
+ /**
267
+ * Gets SLP symbol configuration
268
+ */
269
+ async getSymbolConfig(indexToken, long) {
270
+ this.validateCache();
271
+ try {
272
+ const rawData = await this.provider.getDynamicFieldObject({
273
+ parentId: this.consts.sudoCore.market,
274
+ name: {
275
+ type: `${this.consts.sudoCore.package}::market::SymbolName<${this.consts.coins[indexToken].module}, ${this.consts.sudoCore.package}::market::${long ? 'LONG' : 'SHORT'}>`,
276
+ value: { dummy_field: false },
277
+ },
278
+ });
279
+ return SLPDataAPI.parseSymbolConfig(rawData);
280
+ }
281
+ catch {
282
+ // If the dynamic field doesn't exist, return null
283
+ console.error('Symbol Config Not Found');
284
+ return null;
285
+ }
286
+ }
287
+ /**
288
+ * Gets price impact configuration for a symbol.
289
+ * Price impact config applies to both long and short directions for the same index token.
290
+ */
291
+ async getPriceImpactConfig(indexToken) {
292
+ this.validateCache();
293
+ try {
294
+ const rawData = await this.provider.getDynamicFieldObject({
295
+ parentId: this.consts.sudoCore.market,
296
+ name: {
297
+ type: `0x699769d16326684d94039c23d8568fe8f438854bc69e681328647d6682425a7b::price_impact::PriceImpactConfigKey<${this.consts.coins[indexToken].module}>`,
298
+ value: { dummy_field: false },
299
+ },
300
+ });
301
+ return SLPDataAPI.parsePriceImpactConfig(rawData);
302
+ }
303
+ catch (e) {
304
+ // If the dynamic field doesn't exist, return null (price impact not configured for this symbol)
305
+ console.error('Error Fetching Price Impact Config:', e);
306
+ return null;
307
+ }
308
+ }
309
+ /**
310
+ * Gets SLP symbol OI funding state (global per symbol)
311
+ */
312
+ async getSymbolOiFundingState(indexToken) {
313
+ this.validateCache();
314
+ try {
315
+ const rawData = await this.provider.getDynamicFieldObject({
316
+ parentId: this.consts.sudoCore.market,
317
+ name: {
318
+ type: `0x719955743d23874522250584b34d19462b41eed2816334adb5744308217f378a::funding::FundingStateKey<${this.consts.coins[indexToken].module}>`,
319
+ value: { dummy_field: false },
320
+ },
321
+ });
322
+ return SLPDataAPI.parseOiFundingState(rawData);
323
+ }
324
+ catch (e) {
325
+ // If the dynamic field doesn't exist, return null
326
+ console.error('Error Fetching SLP Symbol OI Funding State:', e);
327
+ return null;
328
+ }
329
+ }
330
+ async getPositionInfoList(positionCapInfoList, owner, batchSize = 10) {
331
+ const positionInfoList = [];
332
+ // Process in batches of 10
333
+ for (let i = 0; i < positionCapInfoList.length; i += batchSize) {
334
+ const batch = positionCapInfoList.slice(i, i + batchSize);
335
+ await Promise.all(batch.map(async (positionCapInfo) => {
336
+ try {
337
+ const positionRaw = await this.provider.getDynamicFieldObject({
338
+ parentId: this.consts.sudoCore.positionsParent,
339
+ name: {
340
+ type: `${this.consts.sudoCore.package}::market::PositionName<${positionCapInfo.symbol0}, ${positionCapInfo.symbol1}, ${this.consts.sudoCore.package}::market::${positionCapInfo.long ? 'LONG' : 'SHORT'}>`,
341
+ value: {
342
+ owner,
343
+ id: positionCapInfo.positionCapId,
344
+ },
345
+ },
346
+ });
347
+ if (positionRaw?.data?.content) {
348
+ positionInfoList.push(await this.parsePositionInfo(positionRaw, positionCapInfo.positionCapId));
349
+ }
350
+ }
351
+ catch (error) {
352
+ // Position might have been deleted after force settlement
353
+ console.warn(`Failed to parse position info for position cap ID ${positionCapInfo.positionCapId}: ${error}`);
354
+ // Continue with next position without adding this one to the list
355
+ }
356
+ }));
357
+ }
358
+ return positionInfoList.sort((a, b) => a.openTimestamp > b.openTimestamp ? 1 : -1);
359
+ }
360
+ /**
361
+ * Gets user history for SLP
362
+ */
363
+ async getHistory(trader, page, limit, orderType, symbol) {
364
+ const params = new URLSearchParams({
365
+ trader,
366
+ page: page.toString(),
367
+ limit: limit.toString(),
368
+ });
369
+ if (orderType) {
370
+ params.append('orderType', orderType);
371
+ }
372
+ if (symbol) {
373
+ params.append('symbol', symbol);
374
+ }
375
+ const url = `${this.apiEndpoint}/traderEvents?${params}`;
376
+ const res = await fetch(url, {
377
+ method: 'GET',
378
+ headers: {
379
+ 'Content-Type': 'application/json',
380
+ },
381
+ });
382
+ const response = await res.json();
383
+ return {
384
+ histories: response.data?.histories || [],
385
+ pagination: response.data?.pagination || {
386
+ total: 0,
387
+ page: 1,
388
+ limit: 20,
389
+ pages: 0,
390
+ },
391
+ };
392
+ }
393
+ async getStaked(owner) {
394
+ let rawCredentialsData = [];
395
+ let queryNextPage = true;
396
+ let queryCursor;
397
+ const limit = 50;
398
+ while (queryNextPage) {
399
+ const { data, hasNextPage, nextCursor } = await this.provider.getOwnedObjects({
400
+ owner,
401
+ filter: {
402
+ MoveModule: {
403
+ package: this.consts.sudoStaking.package,
404
+ module: 'pool',
405
+ },
406
+ },
407
+ options: {
408
+ showType: true,
409
+ showContent: true,
410
+ },
411
+ cursor: queryCursor,
412
+ limit,
413
+ });
414
+ queryNextPage = hasNextPage;
415
+ queryCursor = nextCursor;
416
+ if (!data)
417
+ break;
418
+ rawCredentialsData = [...rawCredentialsData, ...data];
419
+ }
420
+ const pool = await this.getStakePool();
421
+ const credentials = rawCredentialsData.map((item) => SLPDataAPI.parseCredential(item, pool));
422
+ return {
423
+ credentials,
424
+ amount: credentials.reduce((acc, cur) => acc + cur.amount, BigInt(0)),
425
+ claimable: credentials.reduce((acc, cur) => acc + cur.claimable, BigInt(0)),
426
+ };
427
+ }
428
+ /**
429
+ * Legacy method: Get stake pool from sudo_staking
430
+ */
431
+ async getStakePool() {
432
+ const raw = await this.provider.getObject({
433
+ id: this.consts.sudoStaking.pool,
434
+ options: {
435
+ showContent: true,
436
+ },
437
+ });
438
+ return SLPDataAPI.parseStakePool(raw);
439
+ }
440
+ async getStakePoolV2() {
441
+ const poolId = this.sharedConfig.zoStaking.pools.slp;
442
+ const raw = await this.provider.getObject({
443
+ id: poolId,
444
+ options: {
445
+ showContent: true,
446
+ },
447
+ });
448
+ return SLPDataAPI.parseStakePool(raw);
449
+ }
450
+ async getStakedV2(owner) {
451
+ let rawCredentialsData = [];
452
+ let queryNextPage = true;
453
+ let queryCursor;
454
+ const limit = 50;
455
+ while (queryNextPage) {
456
+ const { data, hasNextPage, nextCursor } = await this.provider.getOwnedObjects({
457
+ owner,
458
+ filter: {
459
+ MoveModule: {
460
+ package: this.sharedConfig.zoStaking.package,
461
+ module: 'pool',
462
+ },
463
+ },
464
+ options: {
465
+ showType: true,
466
+ showContent: true,
467
+ },
468
+ cursor: queryCursor,
469
+ limit,
470
+ });
471
+ queryNextPage = hasNextPage;
472
+ queryCursor = nextCursor;
473
+ if (!data)
474
+ break;
475
+ rawCredentialsData = [...rawCredentialsData, ...data];
476
+ }
477
+ const pool = await this.getStakePoolV2();
478
+ const credentials = rawCredentialsData
479
+ .filter((item) => item.data.type
480
+ === `${this.sharedConfig.zoStaking.package}::pool::Credential<${this.consts.sudoCore.package}::slp::SLP, ${this.consts.sudoCore.package}::slp::SLP>`)
481
+ .map((item) => SLPDataAPI.parseCredential(item, pool));
482
+ return {
483
+ credentials,
484
+ amount: credentials.reduce((acc, cur) => acc + cur.amount, BigInt(0)),
485
+ claimable: credentials.reduce((acc, cur) => acc + cur.claimable, BigInt(0)),
486
+ };
487
+ }
488
+ async fundingFeeRate(indexToken, long) {
489
+ const oiState = await this.getSymbolOiFundingState(indexToken);
490
+ if (oiState && oiState.enabled) {
491
+ const longSymbol = await this.getSymbolInfo(indexToken, true);
492
+ const shortSymbol = await this.getSymbolInfo(indexToken, false);
493
+ if (longSymbol.lastUpdate <= 0 && shortSymbol.lastUpdate <= 0) {
494
+ return 0;
495
+ }
496
+ const elapsed = SECONDS_PER_EIGHT_HOUR;
497
+ const deltaRate = SLPDataAPI.calcOiFundingFeeRate(oiState.model, longSymbol.openingSize, shortSymbol.openingSize, elapsed);
498
+ return long ? deltaRate : -deltaRate;
499
+ }
500
+ const symbol = await this.getSymbolInfo(indexToken, long);
501
+ if (symbol.lastUpdate <= 0) {
502
+ return 0;
503
+ }
504
+ const price = (await this.getOraclePrice(indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked();
505
+ const lpSupplyAmount = (await this.getMarketInfo()).lpSupplyWithDecimals;
506
+ const model = symbol.fundingFeeModel;
507
+ const elapsed = SECONDS_PER_EIGHT_HOUR;
508
+ const deltaSize = SLPDataAPI.calcDeltaSize(symbol, price, symbol.long);
509
+ const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount;
510
+ return SLPDataAPI.calcFundingFeeRate(model, pnlPerLp, elapsed);
511
+ }
512
+ async rebaseFeeRate(collateralToken, increase, amount, _sender) {
513
+ let vaultValue = 0;
514
+ if (!increase && amount > 0) {
515
+ amount = -amount;
516
+ }
517
+ const value = amount * (await this.getOraclePrice(collateralToken)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[collateralToken].decimals);
518
+ const vaultPromises = Object.keys(this.consts.sudoCore.vaults).map(async (vault) => {
519
+ const vaultInfo = await this.getVaultInfo(vault);
520
+ const reservingFeeDelta = SLPDataAPI.calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000);
521
+ const totalVaultAmount = reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount;
522
+ const oraclePrice = (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked();
523
+ const res = totalVaultAmount * oraclePrice / (10 ** this.consts.coins[vault].decimals);
524
+ if (collateralToken === vault) {
525
+ vaultValue = res;
526
+ }
527
+ return res;
528
+ });
529
+ const vaultValues = await Promise.all(vaultPromises);
530
+ const totalVaultValue = vaultValues.reduce((acc, curr) => acc + curr, 0);
531
+ const targetRatio = Number.parseInt(this.consts.sudoCore.vaults[collateralToken].weight, 10) / Object.values(this.consts.sudoCore.vaults)
532
+ .map(e => Number.parseInt(e.weight, 10))
533
+ .reduce((acc, curr) => acc + curr, 0);
534
+ return SLPDataAPI.calcRebaseFeeRate(await this.getRebaseFeeModel(), increase, (vaultValue + value) / (totalVaultValue + value), targetRatio);
535
+ }
536
+ async reservingFeeRate(collateralToken, amount, sender) {
537
+ if (!sender) {
538
+ throw new Error('Sender address is required for reservingFeeRate calculation');
539
+ }
540
+ const vaultInfo = await this.getVaultInfo(collateralToken);
541
+ const vaultSupply = vaultInfo.liquidity
542
+ + vaultInfo.reservedAmount
543
+ + vaultInfo.unrealisedReservingFeeAmount
544
+ + amount;
545
+ const utilization = vaultSupply
546
+ ? Number.parseInt((((vaultInfo.reservedAmount + amount) / vaultSupply) * 1e18).toFixed(0), 10)
547
+ : 0;
548
+ const tx = new Transaction();
549
+ tx.moveCall({
550
+ target: `${this.consts.sudoCore.upgradedPackage}::model::compute_reserving_fee_rate`,
551
+ arguments: [
552
+ tx.object(this.consts.sudoCore.vaults[collateralToken].reservingFeeModel),
553
+ tx.pure.u128(utilization),
554
+ tx.pure.u64(8 * 3600),
555
+ ],
556
+ });
557
+ const res = await this.provider.devInspectTransactionBlock({
558
+ transactionBlock: tx,
559
+ sender,
560
+ });
561
+ const de = Rate.parse(new Uint8Array(res.results.at(-1).returnValues[0][0]));
562
+ return Number(BigInt(de)) / 1e18;
563
+ }
564
+ async getPositionConfig(indexToken, long) {
565
+ this.validateCache();
566
+ const symbol = joinSymbol(long ? 'long' : 'short', indexToken);
567
+ if (this.positionConfigCache[symbol]) {
568
+ return this.positionConfigCache[symbol];
569
+ }
570
+ const rawData = await this.provider.getObject({
571
+ id: this.consts.sudoCore.symbols[symbol].positionConfig,
572
+ options: {
573
+ showContent: true,
574
+ },
575
+ });
576
+ return SLPDataAPI.parsePositionConfig(rawData);
577
+ }
578
+ async getOpenPositions(batchSize = 50, symbol = 'sui') {
579
+ let positionDynamicFields = [];
580
+ let _continue = true;
581
+ let cursor;
582
+ while (_continue) {
583
+ // data here will be a list of dynamic fields containing name and value
584
+ const { data, nextCursor, hasNextPage } = await this.provider.getDynamicFields({
585
+ parentId: this.consts.sudoCore.positionsParent,
586
+ cursor,
587
+ });
588
+ positionDynamicFields = positionDynamicFields.concat(data);
589
+ _continue = hasNextPage;
590
+ cursor = nextCursor;
591
+ }
592
+ // Filter by symbol if provided
593
+ if (!(symbol && this.consts.coins[symbol])) {
594
+ return [];
595
+ }
596
+ const coinModule = symbol === 'sui' ? '0x2::sui::SUI' : this.consts.coins[symbol].module;
597
+ positionDynamicFields = positionDynamicFields.filter((field) => {
598
+ // Extract the second coin module from PositionName<coin1, coin2, direction>
599
+ const typeStr = field.name?.type;
600
+ if (!typeStr)
601
+ return false;
602
+ const match = typeStr.match(/PositionName<([^,]+),([^,]+),([^>]+)>/);
603
+ if (!match)
604
+ return false;
605
+ const secondCoin = match[2].trim();
606
+ return secondCoin === coinModule;
607
+ });
608
+ // then we query by dynamic field names and order by time
609
+ const positionInfoList = [];
610
+ for (let i = 0; i < positionDynamicFields.length; i += batchSize) {
611
+ const batch = positionDynamicFields.slice(i, i + batchSize);
612
+ await Promise.all(batch.map(async (positionDynamicField) => {
613
+ const positionRaw = await this.provider.getDynamicFieldObject({
614
+ parentId: this.consts.sudoCore.positionsParent,
615
+ name: positionDynamicField.name,
616
+ });
617
+ if (positionRaw?.data?.content) {
618
+ // @ts-expect-error: content fields type is not properly defined
619
+ if (positionRaw?.data?.content?.fields?.value?.fields?.closed) {
620
+ // skip closed positions
621
+ return;
622
+ }
623
+ const positionInfo = await this.parsePositionInfo(positionRaw, positionDynamicField.objectId);
624
+ if (positionInfo) {
625
+ positionInfoList.push(positionInfo);
626
+ }
627
+ }
628
+ }));
629
+ }
630
+ return positionInfoList
631
+ .filter(positionInfo => !positionInfo.closed)
632
+ .sort((a, b) => (a.openTimestamp > b.openTimestamp ? 1 : -1));
633
+ }
634
+ async getPositionCapInfoList(owner) {
635
+ let cursor;
636
+ let hasNextPage = true;
637
+ const positionCapInfoList = [];
638
+ while (hasNextPage) {
639
+ const positionCaps = await this.provider.getOwnedObjects({
640
+ owner,
641
+ filter: {
642
+ StructType: `${this.consts.sudoCore.package}::market::PositionCap`,
643
+ },
644
+ options: {
645
+ showType: true,
646
+ },
647
+ cursor,
648
+ });
649
+ for (const positionCap of positionCaps.data) {
650
+ if (positionCap.data?.type?.includes('PositionCap')) {
651
+ positionCapInfoList.push({
652
+ positionCapId: positionCap.data.objectId,
653
+ symbol0: positionCap.data.type.split('<')[1].split(',')[0].trim(),
654
+ symbol1: positionCap.data.type
655
+ .split('<')[1]
656
+ .split(',')[1]
657
+ .split(',')[0]
658
+ .trim(),
659
+ long: positionCap.data.type.includes('LONG'),
660
+ });
661
+ }
662
+ }
663
+ hasNextPage = positionCaps.hasNextPage;
664
+ cursor = positionCaps.nextCursor;
665
+ }
666
+ return positionCapInfoList;
667
+ }
668
+ async getOrderCapInfoList(owner) {
669
+ let cursor;
670
+ let hasNextPage = true;
671
+ const orderCapInfoList = [];
672
+ while (hasNextPage) {
673
+ const orderCaps = await this.provider.getOwnedObjects({
674
+ owner,
675
+ filter: {
676
+ StructType: `${this.consts.sudoCore.package}::market::OrderCap`,
677
+ },
678
+ options: {
679
+ showType: true,
680
+ showContent: true,
681
+ },
682
+ cursor,
683
+ });
684
+ for (const orderCap of orderCaps.data) {
685
+ if (orderCap.data?.type?.includes('OrderCap')) {
686
+ orderCapInfoList.push({
687
+ orderCapId: orderCap.data.objectId,
688
+ symbol0: orderCap.data.type.split('<')[1].split(',')[0].trim(),
689
+ symbol1: orderCap.data.type
690
+ .split('<')[1]
691
+ .split(',')[1]
692
+ .split(',')[0]
693
+ .trim(),
694
+ long: orderCap.data.type.includes('LONG'),
695
+ positionId: orderCap.data.content?.fields?.position_id,
696
+ });
697
+ }
698
+ }
699
+ hasNextPage = orderCaps.hasNextPage;
700
+ cursor = orderCaps.nextCursor;
701
+ }
702
+ return orderCapInfoList;
703
+ }
704
+ async hasReferral(referree) {
705
+ const raw = await this.getReferralData(referree);
706
+ return !raw.error;
707
+ }
708
+ async getReferralData(referree) {
709
+ const raw = await this.provider.getDynamicFieldObject({
710
+ parentId: this.consts.sudoCore.referralsParent,
711
+ name: {
712
+ type: 'address',
713
+ value: referree,
714
+ },
715
+ });
716
+ return raw;
717
+ }
718
+ /**
719
+ * Gets user orders for SLP
720
+ */
721
+ async getOrderInfoList(orderCapInfoList, owner, batchSize = 10) {
722
+ const orderInfoList = [];
723
+ // Process in batches of 10
724
+ for (let i = 0; i < orderCapInfoList.length; i += batchSize) {
725
+ const batch = orderCapInfoList.slice(i, i + batchSize);
726
+ await Promise.all(batch.map(async (orderCapInfo) => {
727
+ try {
728
+ let orderRaw = await this.provider.getDynamicFieldObject({
729
+ parentId: this.consts.sudoCore.ordersParent,
730
+ name: {
731
+ type: `${this.consts.sudoCore.package}::market::OrderName<${orderCapInfo.symbol0}, ${orderCapInfo.symbol1}, ${this.consts.sudoCore.package}::market::${orderCapInfo.long ? 'LONG' : 'SHORT'}, ${orderCapInfo.symbol0}>`,
732
+ value: {
733
+ owner,
734
+ id: orderCapInfo.orderCapId,
735
+ position_id: {
736
+ vec: orderCapInfo.positionId ? [orderCapInfo.positionId] : [],
737
+ },
738
+ },
739
+ },
740
+ });
741
+ if (orderRaw?.error?.code === 'dynamicFieldNotFound') {
742
+ orderRaw = await this.provider.getDynamicFieldObject({
743
+ parentId: this.consts.sudoCore.ordersParent,
744
+ name: {
745
+ type: `${this.consts.sudoCore.package}::market::OrderName<${orderCapInfo.symbol0}, ${orderCapInfo.symbol1}, ${this.consts.sudoCore.package}::market::${orderCapInfo.long ? 'LONG' : 'SHORT'}, ${this.consts.coins.sui.module}>`,
746
+ value: {
747
+ owner,
748
+ id: orderCapInfo.orderCapId,
749
+ position_id: {
750
+ vec: orderCapInfo.positionId ? [orderCapInfo.positionId] : [],
751
+ },
752
+ },
753
+ },
754
+ });
755
+ }
756
+ orderInfoList.push(this.parseOrderInfo(orderRaw, orderCapInfo.orderCapId));
757
+ }
758
+ catch (error) {
759
+ // Order might have been deleted
760
+ console.warn(`Failed to parse order info for order cap ID ${orderCapInfo.orderCapId}: ${error}`);
761
+ // Continue with next order without adding this one to the list
762
+ }
763
+ }));
764
+ }
765
+ return orderInfoList.sort((a, b) => (a.createdAt > b.createdAt ? 1 : -1));
766
+ }
767
+ async getCumulativeApr() {
768
+ const refetchDate = new Date(Date.now() - 3600000);
769
+ // fetch new every hour
770
+ if (!aprResponse?.generatedAt
771
+ || (aprResponse?.generatedAt
772
+ && refetchDate > new Date(aprResponse?.generatedAt))) {
773
+ try {
774
+ const url = `${this.apiEndpoint}/cumulativeApr`;
775
+ const res = await fetch(url, {
776
+ method: 'GET',
777
+ headers: {
778
+ 'Content-Type': 'application/json',
779
+ },
780
+ });
781
+ const data = await res.json();
782
+ // eslint-disable-next-line require-atomic-updates
783
+ aprResponse = { ...data };
784
+ return data.cumulativeApr;
785
+ }
786
+ catch {
787
+ console.error('Failed to get cumulative APR');
788
+ }
789
+ return 0;
790
+ }
791
+ return aprResponse.apr;
792
+ }
793
+ // Private helper methods
794
+ static calculatePositionFundingFee(position, symbol, model, price, lpSupplyAmount, timestamp, oiModel, pairedOpeningSize) {
795
+ const accFundingRate = this.calcAccFundingFeeRate(symbol, model, price, lpSupplyAmount, timestamp, position.long, oiModel, pairedOpeningSize);
796
+ return position.fundingFeeValue + (accFundingRate - position.lastFundingRate) * position.positionSize;
797
+ }
798
+ static calcAccFundingFeeRate(symbol, model, price, lpSupplyAmount, timestamp, isLong, oiModel, pairedOpeningSize) {
799
+ if (symbol.lastUpdate > 0) {
800
+ const elapsed = timestamp - symbol.lastUpdate;
801
+ if (elapsed > 0) {
802
+ // Prefer OI-based delta when OI model and paired side are available
803
+ if (oiModel && typeof pairedOpeningSize === 'number') {
804
+ const longSize = isLong ? symbol.openingSize : pairedOpeningSize;
805
+ const shortSize = isLong ? pairedOpeningSize : symbol.openingSize;
806
+ const deltaRate = SLPDataAPI.calcOiFundingFeeRate(oiModel, longSize, shortSize, elapsed);
807
+ const appliedRate = isLong ? deltaRate : -deltaRate;
808
+ return symbol.accFundingRate + appliedRate;
809
+ }
810
+ // Fallback to PnL-based funding delta
811
+ const deltaSize = this.calcDeltaSize(symbol, price, isLong);
812
+ const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount;
813
+ return symbol.accFundingRate + SLPDataAPI.calcFundingFeeRate(model, pnlPerLp, elapsed);
814
+ }
815
+ }
816
+ return symbol.accFundingRate;
817
+ }
818
+ static calcDeltaSize(symbol, price, isLong) {
819
+ const latestSize = symbol.openingAmount / symbol.priceConfig.precision * price;
820
+ return isLong ? symbol.openingSize - latestSize : latestSize - symbol.openingSize;
821
+ }
822
+ static calcFundingFeeRate(model, pnlPerRate, elapsed) {
823
+ const dailyRate = Math.min(model.multiplier * Math.abs(pnlPerRate), model.max);
824
+ const secondsRate = dailyRate * elapsed / SECONDS_PER_EIGHT_HOUR;
825
+ return pnlPerRate >= 0 ? -secondsRate : secondsRate;
826
+ }
827
+ static calcOiFundingFeeRate(model, longSize, shortSize, elapsed) {
828
+ const imbalance = Math.abs(longSize - shortSize);
829
+ const denom = longSize + shortSize > 0 ? longSize + shortSize : 1;
830
+ const dailyRate = Math.min((model.multiplier * (imbalance ** model.exponent)) / denom, model.max);
831
+ const secondsRate = dailyRate * elapsed / SECONDS_PER_EIGHT_HOUR;
832
+ return longSize >= shortSize ? secondsRate : -secondsRate;
833
+ }
834
+ static calculatePositionReserveFee(position, vault, model, timestamp) {
835
+ const accReservingRate = SLPDataAPI.calcAccReservingFeeRate(vault, model, timestamp);
836
+ return position.reservingFeeAmount + (accReservingRate - position.lastReservingRate) * position.reservedAmount;
837
+ }
838
+ static calcAccReservingFeeRate(vault, model, timestamp) {
839
+ if (vault.lastUpdate > 0) {
840
+ const elapsed = timestamp - vault.lastUpdate;
841
+ if (elapsed > 0) {
842
+ const utilization = SLPDataAPI.vaultUtilization(vault);
843
+ return vault.accReservingRate + SLPDataAPI.calcReservingFeeRate(model, utilization, elapsed);
844
+ }
845
+ }
846
+ return vault.accReservingRate;
847
+ }
848
+ static vaultUtilization(vault) {
849
+ const supplyAmount = vault.liquidity + vault.reservedAmount + vault.unrealisedReservingFeeAmount;
850
+ if (supplyAmount === 0) {
851
+ return 0;
852
+ }
853
+ return vault.reservedAmount / supplyAmount;
854
+ }
855
+ static calcReservingFeeRate(model, utilization, elapsed) {
856
+ return model.multiplier * utilization * elapsed / SECONDS_PER_EIGHT_HOUR;
857
+ }
858
+ static parsePositionConfig(raw) {
859
+ const positionConfigFields = raw.data.content.fields.inner.fields;
860
+ return {
861
+ decreaseFeeBps: parseValue(positionConfigFields.decrease_fee_bps),
862
+ liquidationBonus: parseValue(positionConfigFields.liquidation_bonus),
863
+ liquidationThreshold: parseValue(positionConfigFields.liquidation_threshold),
864
+ maxLeverage: parseValue(positionConfigFields.max_leverage),
865
+ minHoldingDuration: parseValue(positionConfigFields.min_holding_duration),
866
+ openFeeBps: parseValue(positionConfigFields.open_fee_bps),
867
+ maxReservedMultiplier: parseValue(positionConfigFields.max_reserved_multiplier),
868
+ minCollateralValue: parseValue(positionConfigFields.min_collateral_value),
869
+ };
870
+ }
871
+ static parseSymbolConfig(raw) {
872
+ const { fields } = raw.data.content;
873
+ return {
874
+ id: fields.id.id,
875
+ max_opening_size: parseValue(fields.max_opening_size),
876
+ max_opening_size_enabled: fields.max_opening_size_enabled,
877
+ max_opening_size_per_position: parseValue(fields.max_opening_size_per_position),
878
+ max_opening_size_per_position_enabled: fields.max_opening_size_per_position_enabled,
879
+ instant_exit_fee_config: {
880
+ instant_exit_tier_1_duration_threshold: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_1_duration_threshold),
881
+ instant_exit_tier_1_fee_bps: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_1_fee_bps.fields.value),
882
+ instant_exit_tier_1_fee_enabled: fields.instant_exit_fee_config.fields.instant_exit_tier_1_fee_enabled,
883
+ instant_exit_tier_2_duration_threshold: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_2_duration_threshold),
884
+ instant_exit_tier_2_fee_bps: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_2_fee_bps.fields.value),
885
+ instant_exit_tier_2_fee_enabled: fields.instant_exit_fee_config.fields.instant_exit_tier_2_fee_enabled,
886
+ instant_exit_tier_3_duration_threshold: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_3_duration_threshold),
887
+ instant_exit_tier_3_fee_bps: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_3_fee_bps.fields.value),
888
+ instant_exit_tier_3_fee_enabled: fields.instant_exit_fee_config.fields.instant_exit_tier_3_fee_enabled,
889
+ },
890
+ };
891
+ }
892
+ static parseRebaseFeeModel(raw) {
893
+ const { fields } = raw.data.content;
894
+ return {
895
+ base: parseValue(fields.base),
896
+ multiplier: parseValue(fields.multiplier),
897
+ };
898
+ }
899
+ static calcRebaseFeeRate(model, increase, ratio, targetRatio) {
900
+ if ((increase && ratio <= targetRatio) || (!increase && ratio >= targetRatio)) {
901
+ return model.base;
902
+ }
903
+ const exponent = model.exponent ?? 1;
904
+ return model.base + model.multiplier * (Math.abs(ratio - targetRatio) ** exponent);
905
+ }
906
+ /**
907
+ * Fetches rebase fee exponent from chain (dynamic field RebaseFeeExponentKey).
908
+ * Returns 1 (linear) for backward compatibility when not set.
909
+ */
910
+ static async getRebaseFeeExponent(provider, rebaseFeeModelId, packageId) {
911
+ const WAD = 1e18;
912
+ try {
913
+ const raw = await provider.getDynamicFieldObject({
914
+ parentId: rebaseFeeModelId,
915
+ name: {
916
+ type: `${packageId}::model::RebaseFeeExponentKey`,
917
+ value: {},
918
+ },
919
+ });
920
+ if (raw.data?.content && 'fields' in raw.data.content) {
921
+ const value = raw.data.content.fields?.value;
922
+ if (value !== undefined) {
923
+ return Number(BigInt(value)) / WAD;
924
+ }
925
+ }
926
+ }
927
+ catch {
928
+ // Dynamic field doesn't exist - use default WAD (linear)
929
+ }
930
+ return 1;
931
+ }
932
+ /**
933
+ * Computes the spread rate based on OI skew between long and short sides using point evaluation.
934
+ * This is the offchain equivalent of price_impact::compute_spread_rate in the Move contract.
935
+ */
936
+ static computeSpreadRate(config, currentOiThisSide, newPositionSize, currentOiOpposite, maxOiThisSide, maxOiOpposite) {
937
+ if (!config.enabled) {
938
+ return 0;
939
+ }
940
+ if (maxOiThisSide === 0) {
941
+ return config.baseSpreadRate;
942
+ }
943
+ const totalOiThis = currentOiThisSide + newPositionSize;
944
+ const thisUtil = Math.min(totalOiThis / maxOiThisSide, 1);
945
+ let oppositeUtil = 0;
946
+ if (maxOiOpposite > 0) {
947
+ oppositeUtil = Math.min(currentOiOpposite / maxOiOpposite, 1);
948
+ }
949
+ let skewRatio = thisUtil - oppositeUtil;
950
+ if (skewRatio < 0) {
951
+ skewRatio = 0;
952
+ }
953
+ else if (skewRatio > 1) {
954
+ skewRatio = 1;
955
+ }
956
+ const exponent = config.impactExponent;
957
+ const exponentInt = Math.floor(exponent);
958
+ const exponentFrac = exponent - exponentInt;
959
+ let ratioPowered = skewRatio ** exponentInt;
960
+ if (exponentFrac > 0) {
961
+ const ratioPoweredNext = skewRatio ** (exponentInt + 1);
962
+ const diff = ratioPoweredNext - ratioPowered;
963
+ if (diff >= 0) {
964
+ ratioPowered += exponentFrac * diff;
965
+ }
966
+ }
967
+ const dynamicSpread = config.maxDynamicSpreadRate * ratioPowered;
968
+ let totalSpread = config.baseSpreadRate + dynamicSpread;
969
+ if (totalSpread > config.maxTotalSpreadRate) {
970
+ totalSpread = config.maxTotalSpreadRate;
971
+ }
972
+ return totalSpread;
973
+ }
974
+ static computeSizeFactor(positionSize, referenceSize) {
975
+ if (referenceSize === 0) {
976
+ return 1;
977
+ }
978
+ const ratio = Math.min(positionSize / referenceSize, 1);
979
+ return 1 + ratio;
980
+ }
981
+ static computeIntegralAverage(maxDynamic, s1, s2, exponent) {
982
+ if (s1 === s2) {
983
+ return maxDynamic * (s1 ** exponent);
984
+ }
985
+ const nPlus1 = exponent + 1;
986
+ const s2Pow = s2 ** nPlus1;
987
+ const s1Pow = s1 ** nPlus1;
988
+ const numerator = Math.abs(s2Pow - s1Pow);
989
+ const sDiff = Math.abs(s2 - s1);
990
+ const denominator = nPlus1 * sDiff;
991
+ if (denominator === 0) {
992
+ return 0;
993
+ }
994
+ return maxDynamic * (numerator / denominator);
995
+ }
996
+ /**
997
+ * Computes the average spread rate over the trade path with size scaling.
998
+ * This is the offchain equivalent of price_impact::compute_average_spread_rate in the Move contract.
999
+ */
1000
+ static computeAverageSpreadRate(config, currentOiThisSide, newPositionSize, currentOiOpposite, maxOiThisSide, maxOiOpposite) {
1001
+ if (!config.enabled) {
1002
+ return 0;
1003
+ }
1004
+ if (maxOiThisSide === 0) {
1005
+ return config.baseSpreadRate;
1006
+ }
1007
+ let oppositeUtil = 0;
1008
+ if (maxOiOpposite > 0) {
1009
+ oppositeUtil = Math.min(currentOiOpposite / maxOiOpposite, 1);
1010
+ }
1011
+ const startUtil = Math.min(currentOiThisSide / maxOiThisSide, 1);
1012
+ const totalOiThis = currentOiThisSide + newPositionSize;
1013
+ const endUtil = Math.min(totalOiThis / maxOiThisSide, 1);
1014
+ let startSkew = startUtil - oppositeUtil;
1015
+ if (startSkew < 0) {
1016
+ startSkew = 0;
1017
+ }
1018
+ else if (startSkew > 1) {
1019
+ startSkew = 1;
1020
+ }
1021
+ let endSkew = endUtil - oppositeUtil;
1022
+ if (endSkew < 0) {
1023
+ endSkew = 0;
1024
+ }
1025
+ else if (endSkew > 1) {
1026
+ endSkew = 1;
1027
+ }
1028
+ const avgDynamicRate = SLPDataAPI.computeIntegralAverage(config.maxDynamicSpreadRate, startSkew, endSkew, config.impactExponent);
1029
+ const sizeFactor = SLPDataAPI.computeSizeFactor(newPositionSize, config.referenceSize);
1030
+ const scaledDynamicRate = avgDynamicRate * sizeFactor;
1031
+ let totalSpread = config.baseSpreadRate + scaledDynamicRate;
1032
+ if (totalSpread > config.maxTotalSpreadRate) {
1033
+ totalSpread = config.maxTotalSpreadRate;
1034
+ }
1035
+ return totalSpread;
1036
+ }
1037
+ /**
1038
+ * Applies price impact to a price.
1039
+ * This is the offchain equivalent of price_impact::apply_price_impact in the Move contract.
1040
+ */
1041
+ static applyPriceImpact(originalPrice, spreadRate, isLong, isOpening) {
1042
+ if (spreadRate === 0) {
1043
+ return originalPrice;
1044
+ }
1045
+ const increasePrice = (isLong && isOpening) || (!isLong && !isOpening);
1046
+ const spreadAmount = originalPrice * spreadRate;
1047
+ if (increasePrice) {
1048
+ return originalPrice + spreadAmount;
1049
+ }
1050
+ const adjusted = originalPrice - spreadAmount;
1051
+ return Math.max(adjusted, 0);
1052
+ }
1053
+ /**
1054
+ * Convenience method to compute the estimated execution price with price impact.
1055
+ */
1056
+ async estimatePriceImpact(indexToken, isLong, isOpening, positionSizeValue) {
1057
+ const config = await this.getPriceImpactConfig(indexToken);
1058
+ if (!config) {
1059
+ return null;
1060
+ }
1061
+ const longSymbol = await this.getSymbolInfo(indexToken, true);
1062
+ const shortSymbol = await this.getSymbolInfo(indexToken, false);
1063
+ const oraclePrice = (await this.getOraclePrice(indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked();
1064
+ const currentOiThisSide = isLong ? longSymbol.openingSize : shortSymbol.openingSize;
1065
+ const currentOiOpposite = isLong ? shortSymbol.openingSize : longSymbol.openingSize;
1066
+ const maxOiThisSide = isLong ? config.maxOiLong : config.maxOiShort;
1067
+ const maxOiOpposite = isLong ? config.maxOiShort : config.maxOiLong;
1068
+ const spreadRate = SLPDataAPI.computeAverageSpreadRate(config, currentOiThisSide, positionSizeValue, currentOiOpposite, maxOiThisSide, maxOiOpposite);
1069
+ const adjustedPrice = SLPDataAPI.applyPriceImpact(oraclePrice, spreadRate, isLong, isOpening);
1070
+ return {
1071
+ spreadRate,
1072
+ originalPrice: oraclePrice,
1073
+ adjustedPrice,
1074
+ };
1075
+ }
1076
+ static parseFundingFeeModel(raw) {
1077
+ const { fields } = raw.data.content;
1078
+ return {
1079
+ multiplier: parseValue(fields.multiplier),
1080
+ max: parseValue(fields.max),
1081
+ };
1082
+ }
1083
+ static parseOiFundingState(raw) {
1084
+ const content = raw.data.content.fields;
1085
+ return {
1086
+ id: content.id.id,
1087
+ enabled: content.enabled,
1088
+ last_update: parseValue(content.last_update),
1089
+ model: {
1090
+ id: content.model.fields.id.id,
1091
+ multiplier: parseValue(content.model.fields.multiplier),
1092
+ exponent: parseValue(content.model.fields.exponent),
1093
+ max: parseValue(content.model.fields.max),
1094
+ },
1095
+ };
1096
+ }
1097
+ static parsePriceImpactConfig(raw) {
1098
+ const content = raw.data.content.fields;
1099
+ return {
1100
+ id: content.id.id,
1101
+ enabled: content.enabled,
1102
+ baseSpreadRate: parseValue(content.base_spread_rate),
1103
+ maxDynamicSpreadRate: parseValue(content.max_dynamic_spread_rate),
1104
+ maxTotalSpreadRate: parseValue(content.max_total_spread_rate),
1105
+ impactExponent: parseValue(content.impact_exponent),
1106
+ maxOiLong: parseValue(content.max_oi_long),
1107
+ maxOiShort: parseValue(content.max_oi_short),
1108
+ referenceSize: parseValue(content.reference_size),
1109
+ };
1110
+ }
1111
+ static parseMarketInfo(raw) {
1112
+ const content = raw.data.content.fields;
1113
+ return {
1114
+ lpSupply: content.lp_supply.fields.value,
1115
+ positionId: content.positions.fields.id.id,
1116
+ vaultId: content.vaults.fields.id.id,
1117
+ symbolId: content.symbols.fields.id.id,
1118
+ lpSupplyWithDecimals: content.lp_supply.fields.value / 10 ** SLP_TOKEN_DECIMALS,
1119
+ };
1120
+ }
1121
+ async parseVaultInfo(raw) {
1122
+ const vaultFields = raw.data.content.fields.value.fields;
1123
+ const reservingFeeModelAddr = vaultFields.reserving_fee_model;
1124
+ const reservingFeeModelRaw = await this.provider.getObject({
1125
+ id: reservingFeeModelAddr,
1126
+ options: {
1127
+ showContent: true,
1128
+ },
1129
+ });
1130
+ const reservingFeeModel = SLPDataAPI.parseReservingFeeModel(reservingFeeModelRaw);
1131
+ return {
1132
+ liquidity: parseValue(vaultFields.liquidity),
1133
+ reservedAmount: parseValue(vaultFields.reserved_amount),
1134
+ unrealisedReservingFeeAmount: parseValue(vaultFields.unrealised_reserving_fee_amount),
1135
+ accReservingRate: parseValue(vaultFields.acc_reserving_rate),
1136
+ enabled: vaultFields.enabled,
1137
+ weight: parseValue(vaultFields.weight),
1138
+ lastUpdate: parseValue(vaultFields.last_update),
1139
+ reservingFeeModel,
1140
+ priceConfig: {
1141
+ maxInterval: parseValue(vaultFields.price_config.fields.max_interval),
1142
+ maxConfidence: parseValue(vaultFields.price_config.fields.max_confidence),
1143
+ precision: parseValue(vaultFields.price_config.fields.precision),
1144
+ feeder: vaultFields.price_config.fields.feeder,
1145
+ },
1146
+ };
1147
+ }
1148
+ static parseReservingFeeModel(raw) {
1149
+ const content = raw.data.content.fields;
1150
+ return {
1151
+ multiplier: parseValue(content.multiplier),
1152
+ };
1153
+ }
1154
+ async parseSymbolInfo(raw, long) {
1155
+ const { objectId } = raw.data;
1156
+ const { fields } = raw.data.content.fields.value;
1157
+ const fundingFeeModelAddr = fields.funding_fee_model;
1158
+ const fundingFeeModelRaw = await this.provider.getObject({
1159
+ id: fundingFeeModelAddr,
1160
+ options: {
1161
+ showContent: true,
1162
+ },
1163
+ });
1164
+ const fundingFeeModel = SLPDataAPI.parseFundingFeeModel(fundingFeeModelRaw);
1165
+ return {
1166
+ objectId,
1167
+ openingSize: parseValue(fields.opening_size),
1168
+ openingAmount: parseValue(fields.opening_amount),
1169
+ accFundingRate: parseValue(fields.acc_funding_rate),
1170
+ realisedPnl: parseValue(fields.realised_pnl),
1171
+ unrealisedFundingFeeValue: parseValue(fields.unrealised_funding_fee_value),
1172
+ openEnabled: fields.open_enabled,
1173
+ liquidateEnabled: fields.liquidate_enabled,
1174
+ decreaseEnabled: fields.decrease_enabled,
1175
+ lastUpdate: parseValue(fields.last_update),
1176
+ fundingFeeModel,
1177
+ long,
1178
+ priceConfig: {
1179
+ maxInterval: parseValue(fields.price_config.fields.max_interval),
1180
+ maxConfidence: parseValue(fields.price_config.fields.max_confidence),
1181
+ precision: parseValue(fields.price_config.fields.precision),
1182
+ feeder: fields.price_config.fields.feeder,
1183
+ },
1184
+ };
1185
+ }
1186
+ async parsePositionInfo(raw, id_) {
1187
+ const { content } = raw.data;
1188
+ const { fields } = content;
1189
+ const positionFields = fields.value.fields;
1190
+ const dataType = fields.name.type;
1191
+ const positionInfo = {
1192
+ id: id_,
1193
+ long: dataType.includes('::market::LONG'),
1194
+ owner: fields.name.fields.owner,
1195
+ version: Number.parseInt(raw.data.version, 10),
1196
+ collateralToken: suiSymbolToSymbol(dataType.split('<')[1].split(',')[0].trim(), this.consts),
1197
+ indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
1198
+ collateralAmount: parseValue(positionFields.collateral),
1199
+ positionAmount: parseValue(positionFields.position_amount),
1200
+ reservedAmount: parseValue(positionFields.reserved),
1201
+ positionSize: parseValue(positionFields.position_size),
1202
+ lastFundingRate: parseValue(positionFields.last_funding_rate),
1203
+ lastReservingRate: parseValue(positionFields.last_reserving_rate),
1204
+ reservingFeeAmount: parseValue(positionFields.reserving_fee_amount),
1205
+ fundingFeeValue: parseValue(positionFields.funding_fee_value),
1206
+ closed: positionFields.closed,
1207
+ openTimestamp: parseValue(positionFields.open_timestamp),
1208
+ protocol: 'sudo',
1209
+ };
1210
+ if (!positionFields.closed) {
1211
+ try {
1212
+ positionInfo.reservingFeeAmount = SLPDataAPI.calculatePositionReserveFee(positionInfo, await this.getVaultInfo(positionInfo.collateralToken), (await this.getVaultInfo(positionInfo.collateralToken)).reservingFeeModel, Date.now() / 1000);
1213
+ // OI context for funding: fetch state and paired side size when enabled
1214
+ const oiState = await this.getSymbolOiFundingState(positionInfo.indexToken);
1215
+ const pairedSymbol = await this.getSymbolInfo(positionInfo.indexToken, !positionInfo.long);
1216
+ positionInfo.fundingFeeValue = SLPDataAPI.calculatePositionFundingFee(positionInfo, await this.getSymbolInfo(positionInfo.indexToken, positionInfo.long), (await this.getSymbolInfo(positionInfo.indexToken, positionInfo.long)).fundingFeeModel, (await this.getOraclePrice(positionInfo.indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked(), (await this.getMarketInfo()).lpSupplyWithDecimals, Date.now() / 1000, oiState && oiState.enabled ? oiState.model : undefined, pairedSymbol.openingSize);
1217
+ }
1218
+ catch (e) {
1219
+ console.error(e);
1220
+ positionInfo.reservingFeeAmount = 0;
1221
+ positionInfo.fundingFeeValue = 0;
1222
+ }
1223
+ }
1224
+ return positionInfo;
1225
+ }
1226
+ parseOrderInfo(raw, capId) {
1227
+ const { content } = raw.data;
1228
+ const { fields } = content.fields.value;
1229
+ // Extract tokens from dataType
1230
+ const dataType = content?.type;
1231
+ let orderType;
1232
+ if (content.fields.value?.type.includes('OpenPositionOrder')) {
1233
+ orderType = 'OPEN_POSITION';
1234
+ }
1235
+ else if (content.fields.value?.type.includes('OpenMarketOrder')) {
1236
+ orderType = 'OPEN_MARKET';
1237
+ }
1238
+ else if (content.fields.value?.type.includes('DecreasePositionOrder')) {
1239
+ orderType = 'DECREASE_POSITION';
1240
+ }
1241
+ else {
1242
+ orderType = 'DECREASE_MARKET';
1243
+ }
1244
+ const orderVersion = content.fields.value?.type.includes('V2')
1245
+ ? 'V2'
1246
+ : 'V1_1';
1247
+ const ret = {
1248
+ id: content.fields.id.id,
1249
+ capId,
1250
+ executed: fields.executed,
1251
+ owner: content.fields.name.fields.owner,
1252
+ collateralToken: suiSymbolToSymbol(dataType.split('<')[2].split(',')[0].trim(), this.consts),
1253
+ indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
1254
+ feeToken: suiSymbolToSymbol(dataType.split(',')[3].split('>')[0].trim(), this.consts),
1255
+ // Use index_price for open orders, limited_index_price for decrease orders
1256
+ indexPrice: orderType === 'OPEN_MARKET'
1257
+ ? parseValue(fields.index_price)
1258
+ : parseValue(fields.limited_index_price?.fields?.price || fields.limited_index_price),
1259
+ collateralPriceThreshold: fields.collateral_price_threshold ? parseValue(fields.collateral_price_threshold) : 0,
1260
+ // New index price threshold
1261
+ indexPriceThreshold: fields.index_price_threshold ? parseValue(fields.index_price_threshold) : undefined,
1262
+ feeAmount: BigInt(fields.fee),
1263
+ long: dataType.includes('::market::LONG'),
1264
+ orderType,
1265
+ orderVersion,
1266
+ createdAt: parseValue(fields.created_at),
1267
+ v11Order: !fields.limited_index_price?.fields?.price,
1268
+ // New: referrer and scard fields
1269
+ referrer: fields.referrer,
1270
+ scardId: (fields.scard_id?.fields?.some?.fields?.id
1271
+ || fields.scard_id?.fields?.value?.fields?.id
1272
+ || fields.scard_id?.fields?.id
1273
+ || fields.scard_id?.id),
1274
+ scardRebateRate: (() => {
1275
+ const inner = fields.scard_rebate_rate?.fields?.some ?? fields.scard_rebate_rate?.fields?.value;
1276
+ return inner ? parseValue(inner) : undefined;
1277
+ })(),
1278
+ };
1279
+ if (orderType === 'OPEN_POSITION' || orderType === 'OPEN_MARKET') {
1280
+ ret.openOrder = {
1281
+ reserveAmount: BigInt(fields.reserve_amount),
1282
+ collateralAmount: BigInt(fields.collateral),
1283
+ openAmount: BigInt(fields.open_amount),
1284
+ // New: position_config from OpenMarketOrder
1285
+ positionConfig: fields.position_config?.fields
1286
+ ? {
1287
+ decreaseFeeBps: parseValue(fields.position_config.fields.decrease_fee_bps),
1288
+ liquidationBonus: parseValue(fields.position_config.fields.liquidation_bonus),
1289
+ liquidationThreshold: parseValue(fields.position_config.fields.liquidation_threshold),
1290
+ maxLeverage: parseValue(fields.position_config.fields.max_leverage),
1291
+ minHoldingDuration: parseValue(fields.position_config.fields.min_holding_duration),
1292
+ openFeeBps: parseValue(fields.position_config.fields.open_fee_bps),
1293
+ maxReservedMultiplier: parseValue(fields.position_config.fields.max_reserved_multiplier),
1294
+ minCollateralValue: parseValue(fields.position_config.fields.min_collateral_value),
1295
+ }
1296
+ : undefined,
1297
+ };
1298
+ }
1299
+ else if (orderType === 'DECREASE_POSITION' || orderType === 'DECREASE_MARKET') {
1300
+ ret.decreaseOrder = {
1301
+ decreaseAmount: BigInt(fields.decrease_amount),
1302
+ takeProfit: fields.take_profit,
1303
+ };
1304
+ }
1305
+ return ret;
1306
+ }
1307
+ static parseCredential(raw, pool) {
1308
+ const stakedAmount = BigInt(raw.data.content.fields.stake);
1309
+ const accRewardPerShare = BigInt(raw.data.content.fields.acc_reward_per_share);
1310
+ return {
1311
+ id: raw.data.objectId,
1312
+ lockUntil: parseValue(raw.data.content.fields.lock_until),
1313
+ amount: stakedAmount,
1314
+ accRewardPerShare,
1315
+ claimable: ((pool.accRewardPerShare - accRewardPerShare) * stakedAmount)
1316
+ / BigInt(1e18),
1317
+ };
1318
+ }
1319
+ static parseStakePool(raw) {
1320
+ const content = raw.data.content.fields;
1321
+ // Check if this is the new version with reward_vault and reward_rate
1322
+ const isNewVersion = content.reward_vault !== undefined && content.reward_rate !== undefined;
1323
+ const pool = {
1324
+ id: content.id.id,
1325
+ enabled: content.enabled,
1326
+ lastUpdatedTime: parseValue(content.last_updated_time),
1327
+ stakedAmount: BigInt(content.staked_amount),
1328
+ // Support both old 'reward' and new 'reward_vault' field names
1329
+ reward: BigInt(content.reward_vault || content.reward),
1330
+ startTime: parseValue(content.start_time),
1331
+ endTime: parseValue(content.end_time),
1332
+ accRewardPerShare: BigInt(content.acc_reward_per_share),
1333
+ lockDuration: parseValue(content.lock_duration),
1334
+ // Add rewardRate if available (new version)
1335
+ ...(isNewVersion && { rewardRate: BigInt(content.reward_rate) }),
1336
+ };
1337
+ // Use appropriate refresh method based on version
1338
+ if (isNewVersion) {
1339
+ SLPDataAPI.refreshPoolV2(pool, Math.floor(Date.now() / 1000));
1340
+ }
1341
+ else {
1342
+ SLPDataAPI.refreshPool(pool, Math.floor(Date.now() / 1000));
1343
+ }
1344
+ return pool;
1345
+ }
1346
+ // Legacy refresh method for backward compatibility
1347
+ static refreshPool(pool, timestamp) {
1348
+ if (timestamp === pool.lastUpdatedTime || timestamp < pool.startTime) {
1349
+ return;
1350
+ }
1351
+ if (pool.lastUpdatedTime === pool.endTime
1352
+ || pool.stakedAmount === BigInt(0)) {
1353
+ return;
1354
+ }
1355
+ if (timestamp > pool.endTime) {
1356
+ timestamp = pool.endTime;
1357
+ }
1358
+ const rewardAmount = (pool.reward * BigInt(timestamp - pool.lastUpdatedTime))
1359
+ / BigInt(pool.endTime - pool.lastUpdatedTime);
1360
+ pool.lastUpdatedTime = timestamp;
1361
+ const rewardPerShare = (rewardAmount * BigInt(1e18)) / pool.stakedAmount;
1362
+ pool.accRewardPerShare += rewardPerShare;
1363
+ }
1364
+ // New refresh method matching SLP implementation
1365
+ static refreshPoolV2(pool, timestamp) {
1366
+ if (timestamp <= pool.lastUpdatedTime || timestamp < pool.startTime) {
1367
+ return;
1368
+ }
1369
+ const applicableEndTime = Math.max(pool.endTime, pool.lastUpdatedTime);
1370
+ const calculationEndTime = Math.min(timestamp, applicableEndTime);
1371
+ if (calculationEndTime > pool.lastUpdatedTime && pool.stakedAmount > BigInt(0) && pool.rewardRate && pool.rewardRate > BigInt(0)) {
1372
+ const timeDiff = BigInt(calculationEndTime - pool.lastUpdatedTime);
1373
+ const rewardAmount = timeDiff * pool.rewardRate;
1374
+ const rewardPerShare = (rewardAmount * BigInt(1e18)) / pool.stakedAmount;
1375
+ pool.accRewardPerShare += rewardPerShare;
1376
+ }
1377
+ pool.lastUpdatedTime = calculationEndTime;
1378
+ }
1379
+ }
1380
+ //# sourceMappingURL=SLPDataAPI.mjs.map