zo-sdk 0.0.50 → 0.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 (206) hide show
  1. package/README.md +272 -20
  2. package/dist/abstract/BaseAPI.cjs +117 -0
  3. package/dist/abstract/BaseAPI.cjs.map +1 -0
  4. package/dist/abstract/BaseAPI.d.cts +131 -0
  5. package/dist/abstract/BaseAPI.d.cts.map +1 -0
  6. package/dist/abstract/BaseAPI.d.mts +131 -0
  7. package/dist/abstract/BaseAPI.d.mts.map +1 -0
  8. package/dist/abstract/BaseAPI.mjs +113 -0
  9. package/dist/abstract/BaseAPI.mjs.map +1 -0
  10. package/dist/abstract/BaseDataAPI.cjs +139 -0
  11. package/dist/abstract/BaseDataAPI.cjs.map +1 -0
  12. package/dist/abstract/BaseDataAPI.d.cts +89 -0
  13. package/dist/abstract/BaseDataAPI.d.cts.map +1 -0
  14. package/dist/abstract/BaseDataAPI.d.mts +89 -0
  15. package/dist/abstract/BaseDataAPI.d.mts.map +1 -0
  16. package/dist/abstract/BaseDataAPI.mjs +135 -0
  17. package/dist/abstract/BaseDataAPI.mjs.map +1 -0
  18. package/dist/abstract/index.cjs +12 -0
  19. package/dist/abstract/index.cjs.map +1 -0
  20. package/dist/abstract/index.d.cts +7 -0
  21. package/dist/abstract/index.d.cts.map +1 -0
  22. package/dist/abstract/index.d.mts +7 -0
  23. package/dist/abstract/index.d.mts.map +1 -0
  24. package/dist/abstract/index.mjs +7 -0
  25. package/dist/abstract/index.mjs.map +1 -0
  26. package/dist/bcs.cjs +42 -0
  27. package/dist/bcs.cjs.map +1 -0
  28. package/dist/bcs.d.cts +91 -0
  29. package/dist/bcs.d.cts.map +1 -0
  30. package/dist/bcs.d.mts +91 -0
  31. package/dist/bcs.d.mts.map +1 -0
  32. package/dist/bcs.mjs +39 -0
  33. package/dist/bcs.mjs.map +1 -0
  34. package/dist/consts/deployments-slp-mainnet.json +710 -0
  35. package/dist/consts/deployments-slp-testnet.json +109 -0
  36. package/dist/consts/deployments-usdz-mainnet.json +180 -0
  37. package/dist/consts/deployments-usdz-testnet.json +98 -0
  38. package/dist/consts/{deployments-mainnet.json → deployments-zlp-mainnet.json} +278 -42
  39. package/dist/consts/index.cjs +40 -8
  40. package/dist/consts/index.cjs.map +1 -1
  41. package/dist/consts/index.d.cts +81 -11
  42. package/dist/consts/index.d.cts.map +1 -1
  43. package/dist/consts/index.d.mts +81 -11
  44. package/dist/consts/index.d.mts.map +1 -1
  45. package/dist/consts/index.mjs +39 -7
  46. package/dist/consts/index.mjs.map +1 -1
  47. package/dist/consts/price_id_to_object_id.mainnet.json +9 -1
  48. package/dist/data.cjs +2 -2
  49. package/dist/data.cjs.map +1 -1
  50. package/dist/data.mjs +2 -2
  51. package/dist/data.mjs.map +1 -1
  52. package/dist/factory/SDKFactory.cjs +185 -0
  53. package/dist/factory/SDKFactory.cjs.map +1 -0
  54. package/dist/factory/SDKFactory.d.cts +74 -0
  55. package/dist/factory/SDKFactory.d.cts.map +1 -0
  56. package/dist/factory/SDKFactory.d.mts +74 -0
  57. package/dist/factory/SDKFactory.d.mts.map +1 -0
  58. package/dist/factory/SDKFactory.mjs +179 -0
  59. package/dist/factory/SDKFactory.mjs.map +1 -0
  60. package/dist/implementations/SLPAPI.cjs +829 -0
  61. package/dist/implementations/SLPAPI.cjs.map +1 -0
  62. package/dist/implementations/SLPAPI.d.cts +120 -0
  63. package/dist/implementations/SLPAPI.d.cts.map +1 -0
  64. package/dist/implementations/SLPAPI.d.mts +120 -0
  65. package/dist/implementations/SLPAPI.d.mts.map +1 -0
  66. package/dist/implementations/SLPAPI.mjs +825 -0
  67. package/dist/implementations/SLPAPI.mjs.map +1 -0
  68. package/dist/implementations/SLPDataAPI.cjs +916 -0
  69. package/dist/implementations/SLPDataAPI.cjs.map +1 -0
  70. package/dist/implementations/SLPDataAPI.d.cts +102 -0
  71. package/dist/implementations/SLPDataAPI.d.cts.map +1 -0
  72. package/dist/implementations/SLPDataAPI.d.mts +102 -0
  73. package/dist/implementations/SLPDataAPI.d.mts.map +1 -0
  74. package/dist/implementations/SLPDataAPI.mjs +912 -0
  75. package/dist/implementations/SLPDataAPI.mjs.map +1 -0
  76. package/dist/implementations/USDZAPI.cjs +522 -0
  77. package/dist/implementations/USDZAPI.cjs.map +1 -0
  78. package/dist/implementations/USDZAPI.d.cts +118 -0
  79. package/dist/implementations/USDZAPI.d.cts.map +1 -0
  80. package/dist/implementations/USDZAPI.d.mts +118 -0
  81. package/dist/implementations/USDZAPI.d.mts.map +1 -0
  82. package/dist/implementations/USDZAPI.mjs +518 -0
  83. package/dist/implementations/USDZAPI.mjs.map +1 -0
  84. package/dist/implementations/USDZDataAPI.cjs +697 -0
  85. package/dist/implementations/USDZDataAPI.cjs.map +1 -0
  86. package/dist/implementations/USDZDataAPI.d.cts +86 -0
  87. package/dist/implementations/USDZDataAPI.d.cts.map +1 -0
  88. package/dist/implementations/USDZDataAPI.d.mts +86 -0
  89. package/dist/implementations/USDZDataAPI.d.mts.map +1 -0
  90. package/dist/implementations/USDZDataAPI.mjs +693 -0
  91. package/dist/implementations/USDZDataAPI.mjs.map +1 -0
  92. package/dist/implementations/ZLPAPI.cjs +809 -0
  93. package/dist/implementations/ZLPAPI.cjs.map +1 -0
  94. package/dist/implementations/ZLPAPI.d.cts +121 -0
  95. package/dist/implementations/ZLPAPI.d.cts.map +1 -0
  96. package/dist/implementations/ZLPAPI.d.mts +121 -0
  97. package/dist/implementations/ZLPAPI.d.mts.map +1 -0
  98. package/dist/implementations/ZLPAPI.mjs +805 -0
  99. package/dist/implementations/ZLPAPI.mjs.map +1 -0
  100. package/dist/implementations/ZLPDataAPI.cjs +724 -0
  101. package/dist/implementations/ZLPDataAPI.cjs.map +1 -0
  102. package/dist/implementations/ZLPDataAPI.d.cts +83 -0
  103. package/dist/implementations/ZLPDataAPI.d.cts.map +1 -0
  104. package/dist/implementations/ZLPDataAPI.d.mts +83 -0
  105. package/dist/implementations/ZLPDataAPI.d.mts.map +1 -0
  106. package/dist/implementations/ZLPDataAPI.mjs +720 -0
  107. package/dist/implementations/ZLPDataAPI.mjs.map +1 -0
  108. package/dist/implementations/index.cjs +22 -0
  109. package/dist/implementations/index.cjs.map +1 -0
  110. package/dist/implementations/index.d.cts +11 -0
  111. package/dist/implementations/index.d.cts.map +1 -0
  112. package/dist/implementations/index.d.mts +11 -0
  113. package/dist/implementations/index.d.mts.map +1 -0
  114. package/dist/implementations/index.mjs +13 -0
  115. package/dist/implementations/index.mjs.map +1 -0
  116. package/dist/index.cjs +47 -0
  117. package/dist/index.cjs.map +1 -1
  118. package/dist/index.d.cts +45 -0
  119. package/dist/index.d.cts.map +1 -1
  120. package/dist/index.d.mts +45 -0
  121. package/dist/index.d.mts.map +1 -1
  122. package/dist/index.mjs +45 -0
  123. package/dist/index.mjs.map +1 -1
  124. package/dist/interfaces/base.cjs +8 -0
  125. package/dist/interfaces/base.cjs.map +1 -0
  126. package/dist/interfaces/base.d.cts +293 -0
  127. package/dist/interfaces/base.d.cts.map +1 -0
  128. package/dist/interfaces/base.d.mts +293 -0
  129. package/dist/interfaces/base.d.mts.map +1 -0
  130. package/dist/interfaces/base.mjs +6 -0
  131. package/dist/interfaces/base.mjs.map +1 -0
  132. package/dist/interfaces/index.cjs +29 -0
  133. package/dist/interfaces/index.cjs.map +1 -0
  134. package/dist/interfaces/index.d.cts +13 -0
  135. package/dist/interfaces/index.d.cts.map +1 -0
  136. package/dist/interfaces/index.d.mts +13 -0
  137. package/dist/interfaces/index.d.mts.map +1 -0
  138. package/dist/interfaces/index.mjs +13 -0
  139. package/dist/interfaces/index.mjs.map +1 -0
  140. package/dist/interfaces/slp.cjs +9 -0
  141. package/dist/interfaces/slp.cjs.map +1 -0
  142. package/dist/interfaces/slp.d.cts +115 -0
  143. package/dist/interfaces/slp.d.cts.map +1 -0
  144. package/dist/interfaces/slp.d.mts +115 -0
  145. package/dist/interfaces/slp.d.mts.map +1 -0
  146. package/dist/interfaces/slp.mjs +7 -0
  147. package/dist/interfaces/slp.mjs.map +1 -0
  148. package/dist/interfaces/usdz.cjs +7 -0
  149. package/dist/interfaces/usdz.cjs.map +1 -0
  150. package/dist/interfaces/usdz.d.cts +40 -0
  151. package/dist/interfaces/usdz.d.cts.map +1 -0
  152. package/dist/interfaces/usdz.d.mts +40 -0
  153. package/dist/interfaces/usdz.d.mts.map +1 -0
  154. package/dist/interfaces/usdz.mjs +6 -0
  155. package/dist/interfaces/usdz.mjs.map +1 -0
  156. package/dist/interfaces/zlp.cjs +7 -0
  157. package/dist/interfaces/zlp.cjs.map +1 -0
  158. package/dist/interfaces/zlp.d.cts +45 -0
  159. package/dist/interfaces/zlp.d.cts.map +1 -0
  160. package/dist/interfaces/zlp.d.mts +45 -0
  161. package/dist/interfaces/zlp.d.mts.map +1 -0
  162. package/dist/interfaces/zlp.mjs +6 -0
  163. package/dist/interfaces/zlp.mjs.map +1 -0
  164. package/dist/oracle.cjs +7 -35
  165. package/dist/oracle.cjs.map +1 -1
  166. package/dist/oracle.d.cts +3 -4
  167. package/dist/oracle.d.cts.map +1 -1
  168. package/dist/oracle.d.mts +3 -4
  169. package/dist/oracle.d.mts.map +1 -1
  170. package/dist/oracle.mjs +8 -32
  171. package/dist/oracle.mjs.map +1 -1
  172. package/package.json +1 -1
  173. package/src/abstract/BaseAPI.ts +429 -0
  174. package/src/abstract/BaseDataAPI.ts +204 -0
  175. package/src/abstract/index.ts +7 -0
  176. package/src/bcs.ts +45 -0
  177. package/src/consts/deployments-slp-mainnet.json +710 -0
  178. package/src/consts/deployments-slp-testnet.json +109 -0
  179. package/src/consts/deployments-usdz-mainnet.json +180 -0
  180. package/src/consts/deployments-usdz-testnet.json +98 -0
  181. package/src/consts/{deployments-mainnet.json → deployments-zlp-mainnet.json} +279 -43
  182. package/src/consts/index.ts +134 -39
  183. package/src/consts/price_id_to_object_id.mainnet.json +10 -2
  184. package/src/data.ts +2 -2
  185. package/src/factory/SDKFactory.ts +282 -0
  186. package/src/implementations/SLPAPI.ts +1207 -0
  187. package/src/implementations/SLPDataAPI.ts +1188 -0
  188. package/src/implementations/USDZAPI.ts +715 -0
  189. package/src/implementations/USDZDataAPI.ts +826 -0
  190. package/src/implementations/ZLPAPI.ts +1130 -0
  191. package/src/implementations/ZLPDataAPI.ts +856 -0
  192. package/src/implementations/index.ts +14 -0
  193. package/src/index.ts +53 -0
  194. package/src/interfaces/base.ts +556 -0
  195. package/src/interfaces/index.ts +45 -0
  196. package/src/interfaces/slp.ts +156 -0
  197. package/src/interfaces/usdz.ts +71 -0
  198. package/src/interfaces/zlp.ts +96 -0
  199. package/src/oracle.ts +12 -42
  200. package/tsconfig.json +4 -2
  201. package/dist/consts/staking/deployments-mainnet.json +0 -12
  202. package/dist/consts/staking/deployments-testnet.json +0 -11
  203. package/src/consts/staking/deployments-mainnet.json +0 -12
  204. package/src/consts/staking/deployments-testnet.json +0 -11
  205. /package/dist/consts/{deployments-testnet.json → deployments-zlp-testnet.json} +0 -0
  206. /package/src/consts/{deployments-testnet.json → deployments-zlp-testnet.json} +0 -0
@@ -0,0 +1,1188 @@
1
+ /**
2
+ * SLP DataAPI implementation
3
+ * Implements SLP-specific data access methods for Sudo SDK
4
+ */
5
+
6
+ import type { DynamicFieldInfo, SuiClient } from '@mysten/sui/client'
7
+ import { Transaction } from '@mysten/sui/transactions'
8
+ import { SUI_CLOCK_OBJECT_ID } from '@mysten/sui/utils'
9
+ import type { Network } from '../consts'
10
+ import { LPToken, SLP_TOKEN_DECIMALS } from '../consts'
11
+ import type {
12
+ ISLPDataAPI,
13
+ ISLPMarketValuationInfo,
14
+ ISLPMarketInfo,
15
+ ISLPVaultInfo,
16
+ ISLPSymbolInfo,
17
+ ISLPPositionInfo,
18
+ ISLPOrderInfo,
19
+ ISLPStakePool,
20
+ ISLPCredential,
21
+ IBaseHistoryResponse,
22
+ ISLPPositionCapInfo,
23
+ ISLPOrderCapInfo,
24
+ ISLPStaked,
25
+ ISLPPositionConfig,
26
+ ISLPFundingFeeModel,
27
+ ISLPReservingFeeModel,
28
+ ISLPRebaseFeeModel
29
+ } from '../interfaces'
30
+ import { BaseDataAPI } from '../abstract'
31
+ import { decimalToObject, joinSymbol, parseSymbolKey, parseValue, suiSymbolToSymbol } from '../utils'
32
+ import { Rate, SRate, SymbolsValuation, VaultsValuation } from '../bcs'
33
+
34
+ export interface GetCumulativeAprResponse {
35
+ generatedAt?: string
36
+ apr?: string
37
+ }
38
+
39
+ let aprResponse: GetCumulativeAprResponse = {}
40
+
41
+ const SECONDS_PER_EIGHT_HOUR = 8 * 60 * 60; // 28800 seconds
42
+
43
+ export class SLPDataAPI extends BaseDataAPI implements ISLPDataAPI {
44
+ constructor(
45
+ network: Network,
46
+ provider: SuiClient,
47
+ apiEndpoint: string,
48
+ connectionURL: string
49
+ ) {
50
+ super(network, provider, apiEndpoint, connectionURL, LPToken.SLP)
51
+ }
52
+
53
+ public async getRebaseFeeModel(): Promise<ISLPRebaseFeeModel> {
54
+ this.validateCache()
55
+ if (this.rebaseFeeModelCache) {
56
+ return this.rebaseFeeModelCache
57
+ }
58
+ const rawData = await this.provider.getObject({
59
+ id: this.consts.zoCore.rebaseFeeModel,
60
+ options: {
61
+ showContent: true,
62
+ },
63
+ })
64
+ return this.parseRebaseFeeModel(rawData)
65
+ }
66
+
67
+ /**
68
+ * Creates vaults valuation for SLP using Sudo SDK approach
69
+ */
70
+ public valuateVaults(tx: Transaction) {
71
+ if (!this.consts.sudoCore) {
72
+ throw new Error('Sudo Core configuration not found. Make sure you are using LPToken.SLP')
73
+ }
74
+
75
+ const vaultsValuation = tx.moveCall({
76
+ target: `${this.consts.sudoCore.upgradedPackage}::market::create_vaults_valuation`,
77
+ typeArguments: [`${this.consts.sudoCore.package}::slp::SLP`],
78
+ arguments: [
79
+ tx.object(SUI_CLOCK_OBJECT_ID),
80
+ tx.object(this.consts.sudoCore.market),
81
+ ],
82
+ })
83
+
84
+ for (const key of Object.keys(this.consts.sudoCore.vaults)) {
85
+ const vault = this.consts.sudoCore.vaults[key]
86
+
87
+ tx.moveCall({
88
+ target: `${this.consts.sudoCore.upgradedPackage}::market::valuate_vault_v1_1`,
89
+ typeArguments: [
90
+ `${this.consts.sudoCore.package}::slp::SLP`,
91
+ this.consts.coins[key].module,
92
+ ],
93
+ arguments: [
94
+ tx.object(this.consts.sudoCore.market),
95
+ tx.object(vault.reservingFeeModel),
96
+ tx.object(this.consts.pythFeeder.feeder[key]),
97
+ vaultsValuation,
98
+ ],
99
+ })
100
+ }
101
+ return vaultsValuation
102
+ }
103
+
104
+ /**
105
+ * Creates symbols valuation for SLP using Sudo SDK approach
106
+ */
107
+ public valuateSymbols(tx: Transaction) {
108
+ if (!this.consts.sudoCore) {
109
+ throw new Error('Sudo Core configuration not found. Make sure you are using LPToken.SLP')
110
+ }
111
+
112
+ const symbolsValuation = tx.moveCall({
113
+ target: `${this.consts.sudoCore.upgradedPackage}::market::create_symbols_valuation`,
114
+ typeArguments: [`${this.consts.sudoCore.package}::slp::SLP`],
115
+ arguments: [
116
+ tx.object(SUI_CLOCK_OBJECT_ID),
117
+ tx.object(this.consts.sudoCore.market),
118
+ ],
119
+ })
120
+ for (const key of Object.keys(this.consts.sudoCore.symbols)) {
121
+ const [direction, token] = parseSymbolKey(key)
122
+ const symbol = this.consts.sudoCore.symbols[key]
123
+ tx.moveCall({
124
+ target: `${this.consts.sudoCore.upgradedPackage}::market::valuate_symbol_v1_1`,
125
+ typeArguments: [
126
+ `${this.consts.sudoCore.package}::slp::SLP`,
127
+ this.consts.coins[token].module,
128
+ `${this.consts.sudoCore.package}::market::${direction.toUpperCase()}`,
129
+ ],
130
+ arguments: [
131
+ tx.object(this.consts.sudoCore.market),
132
+ tx.object(symbol.fundingFeeModel),
133
+ tx.object(this.consts.pythFeeder.feeder[token]),
134
+ symbolsValuation,
135
+ ],
136
+ })
137
+ }
138
+ return symbolsValuation
139
+ }
140
+
141
+ /**
142
+ * Creates both vaults and symbols valuation for SLP
143
+ */
144
+ public valuate(tx: Transaction) {
145
+ const vaultsValuation = this.valuateVaults(tx)
146
+ const symbolsValuation = this.valuateSymbols(tx)
147
+ return { vaultsValuation, symbolsValuation }
148
+ }
149
+
150
+ /**
151
+ * Valuates the SLP market using Sudo SDK's approach
152
+ */
153
+ public async valuateMarket(): Promise<ISLPMarketValuationInfo> {
154
+ const marketInfo = await this.getMarketInfo()
155
+ let slpPrice = 0
156
+ let value = 0
157
+ value = await this.simValuate(this.consts.sudoCore.adminCap)
158
+ slpPrice = value / marketInfo.lpSupplyWithDecimals
159
+
160
+ return {
161
+ marketCap: value,
162
+ price: slpPrice,
163
+ supply: marketInfo.lpSupplyWithDecimals,
164
+ apr: Number(marketInfo.apr),
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Valuates market using simulation (Sudo SDK specific)
170
+ */
171
+ public async simValuate(sender: string): Promise<number> {
172
+ const tx = await this.initOracleTxb(
173
+ Object.keys(this.consts.pythFeeder.feeder),
174
+ )
175
+ this.valuate(tx)
176
+ const res = await this.provider.devInspectTransactionBlock({
177
+ transactionBlock: tx,
178
+ sender,
179
+ })
180
+
181
+ const symbolsValuationOffset =
182
+ Object.keys(this.consts.sudoCore.symbols).length + 1
183
+
184
+ const vaultsValuation = VaultsValuation.parse(
185
+ new Uint8Array(
186
+ (
187
+ (res.results as any)[
188
+ (res.results?.length || 0) - symbolsValuationOffset - 1
189
+ ].mutableReferenceOutputs as any
190
+ )[1][1],
191
+ ),
192
+ )
193
+
194
+ const symbolsValuation = SymbolsValuation.parse(
195
+ new Uint8Array(
196
+ (
197
+ (res.results as any)[(res.results?.length || 0) - 1]
198
+ .mutableReferenceOutputs as any
199
+ )[1][1],
200
+ ),
201
+ )
202
+
203
+ const result =
204
+ Number(
205
+ BigInt(vaultsValuation.value) +
206
+ BigInt(symbolsValuation.value.value) *
207
+ BigInt(symbolsValuation.value.is_positive ? 1 : -1),
208
+ ) / 1e18
209
+ return result
210
+ }
211
+
212
+ /**
213
+ * Valuates only vaults (Sudo SDK specific)
214
+ */
215
+ public async simValuateVaults(sender: string): Promise<number> {
216
+ const tx = await this.initOracleTxb(
217
+ Object.keys(this.consts.sudoCore.vaults),
218
+ )
219
+ this.valuateVaults(tx)
220
+
221
+ const res = await this.provider.devInspectTransactionBlock({
222
+ transactionBlock: tx,
223
+ sender,
224
+ })
225
+ const vaultsValuation = VaultsValuation.parse(
226
+ new Uint8Array(
227
+ (
228
+ (res.results as any)[(res.results?.length || 0) - 1]
229
+ .mutableReferenceOutputs as any
230
+ )[1][1],
231
+ ),
232
+ )
233
+
234
+
235
+ const result = Number(BigInt(vaultsValuation.value)) / 1e18
236
+ return result
237
+ }
238
+
239
+ /**
240
+ * Valuates market with vaults only
241
+ */
242
+ public async valuateMarketWithVaultsOnly(): Promise<ISLPMarketValuationInfo> {
243
+ if (!this.consts.sudoCore) {
244
+ throw new Error('Sudo Core configuration not found')
245
+ }
246
+
247
+ const marketInfo = await this.getMarketInfo()
248
+ let value = await this.simValuateVaults(this.consts.sudoCore.adminCap)
249
+ let slpPrice = value / marketInfo.lpSupplyWithDecimals
250
+
251
+ return {
252
+ marketCap: value,
253
+ price: slpPrice,
254
+ supply: marketInfo.lpSupplyWithDecimals,
255
+ apr: Number(marketInfo.apr),
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Gets SLP market information
261
+ */
262
+ public async getMarketInfo(): Promise<ISLPMarketInfo> {
263
+ this.validateCache()
264
+ if (this.marketInfoCache) {
265
+ return this.marketInfoCache
266
+ }
267
+
268
+ const rawData = await this.provider.getObject({
269
+ id: this.consts.sudoCore.market,
270
+ options: {
271
+ showContent: true,
272
+ },
273
+ })
274
+ const apr = await this.getCumulativeApr()
275
+ return {
276
+ ...this.parseMarketInfo(rawData),
277
+ apr,
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Gets SLP vault information
283
+ */
284
+ public async getVaultInfo(vaultToken: string): Promise<ISLPVaultInfo> {
285
+ this.validateCache()
286
+ if (this.vaultInfoCache[vaultToken]) {
287
+ return this.vaultInfoCache[vaultToken]
288
+ }
289
+
290
+ const rawData = await this.provider.getDynamicFieldObject({
291
+ parentId: this.consts.sudoCore.vaultsParent,
292
+ name: {
293
+ type: `${this.consts.sudoCore.package}::market::VaultName<${this.consts.coins[vaultToken].module}>`,
294
+ value: { dummy_field: false },
295
+ },
296
+ })
297
+ return await this.parseVaultInfo(rawData)
298
+ }
299
+
300
+ /**
301
+ * Gets SLP symbol information
302
+ */
303
+ public async getSymbolInfo(indexToken: string, long: boolean): Promise<ISLPSymbolInfo> {
304
+ this.validateCache();
305
+ const symbol = joinSymbol(long ? 'long' : 'short', indexToken);
306
+ if (this.symbolInfoCache[symbol]) {
307
+ return this.symbolInfoCache[symbol];
308
+ }
309
+
310
+ const rawData = await this.provider.getDynamicFieldObject({
311
+ parentId: this.consts.sudoCore.symbolsParent,
312
+ name: {
313
+ type: `${this.consts.sudoCore.package}::market::SymbolName<${this.consts.coins[indexToken].module
314
+ }, ${this.consts.sudoCore.package}::market::${long ? 'LONG' : 'SHORT'
315
+ }>`,
316
+ value: { dummy_field: false },
317
+ },
318
+ })
319
+
320
+ return await this.parseSymbolInfo(rawData, long)
321
+ }
322
+
323
+ public async getPositionInfoList(
324
+ positionCapInfoList: ISLPPositionCapInfo[],
325
+ owner: string
326
+ ): Promise<ISLPPositionInfo[]> {
327
+ const positionInfoList: ISLPPositionInfo[] = []
328
+ await Promise.all(
329
+ positionCapInfoList.map(async positionCapInfo => {
330
+ const positionRaw = await this.provider.getDynamicFieldObject({
331
+ parentId: this.consts.sudoCore.positionsParent,
332
+ name: {
333
+ type: `${this.consts.sudoCore.package}::market::PositionName<${positionCapInfo.symbol0
334
+ }, ${positionCapInfo.symbol1}, ${this.consts.sudoCore.package
335
+ }::market::${positionCapInfo.long ? 'LONG' : 'SHORT'}>`,
336
+ value: {
337
+ owner,
338
+ id: positionCapInfo.positionCapId,
339
+ },
340
+ },
341
+ })
342
+
343
+ if (positionRaw?.data?.content) {
344
+ positionInfoList.push(
345
+ await this.parsePositionInfo(
346
+ positionRaw,
347
+ positionCapInfo.positionCapId,
348
+ ),
349
+ )
350
+ }
351
+ }),
352
+ )
353
+
354
+ return positionInfoList.sort((a, b) =>
355
+ a.openTimestamp > b.openTimestamp ? 1 : -1,
356
+ )
357
+ }
358
+
359
+
360
+ /**
361
+ * Gets user history for SLP
362
+ */
363
+ public async getHistory(
364
+ trader: string,
365
+ page: number,
366
+ limit: number,
367
+ orderType?: string,
368
+ symbol?: string
369
+ ): Promise<IBaseHistoryResponse> {
370
+ const params = new URLSearchParams({
371
+ trader,
372
+ page: page.toString(),
373
+ limit: limit.toString(),
374
+ })
375
+
376
+ if (orderType) {
377
+ params.append('orderType', orderType)
378
+ }
379
+ if (symbol) {
380
+ params.append('symbol', symbol)
381
+ }
382
+
383
+ const url = `${this.apiEndpoint}/traderEvents?${params}`
384
+ const res = await fetch(url, {
385
+ method: 'GET',
386
+ headers: {
387
+ 'Content-Type': 'application/json',
388
+ },
389
+ })
390
+ const response = await res.json() as any
391
+
392
+ return {
393
+ histories: response.data?.histories || [],
394
+ pagination: response.data?.pagination || {
395
+ total: 0,
396
+ page: 1,
397
+ limit: 20,
398
+ pages: 0,
399
+ },
400
+ }
401
+ }
402
+
403
+ public async getStaked(owner: string): Promise<ISLPStaked> {
404
+ let rawCredentialsData: any[] = []
405
+ let queryNextPage = true
406
+ let queryCursor = undefined
407
+ const limit = 50
408
+
409
+ while (queryNextPage) {
410
+ const { data, hasNextPage, nextCursor } = await this.provider.getOwnedObjects({
411
+ owner,
412
+ filter: {
413
+ MoveModule: {
414
+ package: this.consts.sudoStaking.package,
415
+ module: 'pool',
416
+ },
417
+ },
418
+ options: {
419
+ showType: true,
420
+ showContent: true,
421
+ },
422
+ cursor: queryCursor,
423
+ limit,
424
+ })
425
+
426
+ queryNextPage = hasNextPage
427
+ queryCursor = nextCursor!
428
+ if (!data) break
429
+ rawCredentialsData = [...rawCredentialsData, ...data]
430
+ }
431
+
432
+ const pool = await this.getStakePool()
433
+ const credentials = rawCredentialsData.map((item: any) =>
434
+ this.parseCredential(item, pool),
435
+ )
436
+ return {
437
+ credentials,
438
+ amount: credentials.reduce(
439
+ (acc: bigint, cur: ISLPCredential) => acc + cur.amount,
440
+ BigInt(0),
441
+ ),
442
+ claimable: credentials.reduce(
443
+ (acc: bigint, cur: ISLPCredential) => acc + cur.claimable,
444
+ BigInt(0),
445
+ ),
446
+ }
447
+ }
448
+
449
+ public async getStakePool(): Promise<ISLPStakePool> {
450
+ const raw = await this.provider.getObject({
451
+ id: this.consts.sudoStaking.pool,
452
+ options: {
453
+ showContent: true,
454
+ },
455
+ })
456
+ return this.parseStakePool(raw)
457
+ }
458
+
459
+ public async fundingFeeRate(indexToken: string, long: boolean, sender: string): Promise<number> {
460
+ const tx = await this.initOracleTxb([indexToken])
461
+ const symbol_ = joinSymbol(long ? 'long' : 'short', indexToken)
462
+ const currentTimestamp = parseInt((+new Date() / 1000).toFixed(0))
463
+ const symbol = tx.moveCall({
464
+ target: `${this.consts.sudoCore.package}::market::symbol`,
465
+ typeArguments: [
466
+ `${this.consts.sudoCore.package}::slp::SLP`,
467
+ this.consts.coins[indexToken].module,
468
+ `${this.consts.sudoCore.package}::market::${long ? 'LONG' : 'SHORT'}`,
469
+ ],
470
+ arguments: [tx.object(this.consts.sudoCore.market)],
471
+ })
472
+ const aggPriceConfig = tx.moveCall({
473
+ target: `${this.consts.sudoCore.package}::pool::symbol_price_config`,
474
+ typeArguments: [],
475
+ arguments: [symbol],
476
+ })
477
+ const aggPrice = tx.moveCall({
478
+ target: `${this.consts.sudoCore.package}::agg_price::parse_pyth_feeder_v1_1`,
479
+ typeArguments: [],
480
+ arguments: [
481
+ aggPriceConfig,
482
+ tx.object(this.consts.pythFeeder.feeder[indexToken]),
483
+ tx.pure.u64(currentTimestamp),
484
+ ],
485
+ })
486
+ const deltaSize = tx.moveCall({
487
+ target: `${this.consts.sudoCore.package}::pool::symbol_delta_size`,
488
+ typeArguments: [],
489
+ arguments: [symbol, aggPrice, tx.pure.bool(long)],
490
+ })
491
+ const LpSupplyAmount = tx.moveCall({
492
+ target: `${this.consts.sudoCore.package}::market::lp_supply_amount`,
493
+ typeArguments: [`${this.consts.sudoCore.package}::slp::SLP`],
494
+ arguments: [tx.object(this.consts.sudoCore.market)],
495
+ })
496
+ const PnlPerLp = tx.moveCall({
497
+ target: `${this.consts.sudoCore.package}::pool::symbol_pnl_per_lp`,
498
+ typeArguments: [],
499
+ arguments: [symbol, deltaSize, LpSupplyAmount],
500
+ })
501
+ tx.moveCall({
502
+ target: `${this.consts.sudoCore.package}::model::compute_funding_fee_rate`,
503
+ arguments: [
504
+ tx.object(this.consts.sudoCore.symbols[symbol_].fundingFeeModel),
505
+ PnlPerLp,
506
+ tx.pure.u64(8 * 3600),
507
+ ],
508
+ })
509
+
510
+ const res: any = await this.provider.devInspectTransactionBlock({
511
+ transactionBlock: tx,
512
+ sender,
513
+ })
514
+
515
+ const de = SRate.parse(
516
+ new Uint8Array(res.results[res.results.length - 1].returnValues[0][0]),
517
+ )
518
+
519
+ return (Number(BigInt(de.value)) / 1e18) * (de.is_positive ? 1 : -1)
520
+ }
521
+
522
+ public async rebaseFeeRate(collateralToken: string, increase: boolean, amount: number, sender: string): Promise<number> {
523
+ const tx1 = await this.initOracleTxb(
524
+ Object.keys(this.consts.pythFeeder.feeder),
525
+ )
526
+ this.valuateVaults(tx1)
527
+ const res1 = await this.provider.devInspectTransactionBlock({
528
+ transactionBlock: tx1,
529
+ sender,
530
+ })
531
+ const vaultsValuation = VaultsValuation.parse(
532
+ new Uint8Array(
533
+ (
534
+ (res1.results as any)[(res1.results?.length || 0) - 1]
535
+ .mutableReferenceOutputs as any
536
+ )[1][1],
537
+ ),
538
+ )
539
+ const singleVaultValue =
540
+ BigInt(
541
+ // @ts-ignore
542
+ vaultsValuation.handled.find((item: any) =>
543
+ (item.key || '').includes(this.consts.coins[collateralToken].module.slice(2)) || false,
544
+ )?.value.value,
545
+ ) + BigInt(Math.floor(amount))
546
+ const allVaultValue = BigInt(vaultsValuation.value) + BigInt(Math.floor(amount))
547
+ const singleVaultWeight = BigInt(
548
+ this.consts.sudoCore.vaults[collateralToken].weight,
549
+ )
550
+ const allVaultWeight = BigInt(vaultsValuation.total_weight)
551
+ const tx2 = new Transaction()
552
+ tx2.moveCall({
553
+ target: `${this.consts.sudoCore.package}::pool::compute_rebase_fee_rate`,
554
+ arguments: [
555
+ tx2.object(this.consts.sudoCore.rebaseFeeModel),
556
+ tx2.pure.bool(increase),
557
+ tx2.pure.u256(singleVaultValue),
558
+ tx2.pure.u256(allVaultValue),
559
+ tx2.pure.u256(singleVaultWeight),
560
+ tx2.pure.u256(allVaultWeight),
561
+ ],
562
+ })
563
+ const res2: any = await this.provider.devInspectTransactionBlock({
564
+ transactionBlock: tx2,
565
+ sender,
566
+ })
567
+ const de = Rate.parse(
568
+ new Uint8Array(res2.results[res2.results.length - 1].returnValues[0][0]),
569
+ )
570
+ return Number(BigInt(de)) / 1e18
571
+ }
572
+
573
+ public async reservingFeeRate(collateralToken: string, amount: number, sender: string): Promise<number> {
574
+ const vaultInfo = await this.getVaultInfo(collateralToken)
575
+ const vaultSupply =
576
+ vaultInfo.liquidity +
577
+ vaultInfo.reservedAmount +
578
+ vaultInfo.unrealisedReservingFeeAmount +
579
+ amount
580
+ const utilization = vaultSupply
581
+ ? parseInt(
582
+ (((vaultInfo.reservedAmount + amount) / vaultSupply) * 1e18).toFixed(
583
+ 0,
584
+ ),
585
+ )
586
+ : 0
587
+ const tx = new Transaction()
588
+ tx.moveCall({
589
+ target: `${this.consts.sudoCore.upgradedPackage}::model::compute_reserving_fee_rate`,
590
+ arguments: [
591
+ tx.object(
592
+ this.consts.sudoCore.vaults[collateralToken].reservingFeeModel,
593
+ ),
594
+ tx.pure.u128(utilization),
595
+ tx.pure.u64(8 * 3600),
596
+ ],
597
+ })
598
+ const res: any = await this.provider.devInspectTransactionBlock({
599
+ transactionBlock: tx,
600
+ sender,
601
+ })
602
+ const de = Rate.parse(
603
+ new Uint8Array(res.results[res.results.length - 1].returnValues[0][0]),
604
+ )
605
+ return Number(BigInt(de)) / 1e18
606
+ }
607
+
608
+ public async getPositionConfig(indexToken: string, long: boolean): Promise<ISLPPositionConfig> {
609
+ this.validateCache()
610
+ const symbol = joinSymbol(long ? 'long' : 'short', indexToken)
611
+ if (this.positionConfigCache[symbol]) {
612
+ return this.positionConfigCache[symbol]
613
+ }
614
+
615
+ const rawData = await this.provider.getObject({
616
+ id: this.consts.sudoCore.symbols[symbol].positionConfig,
617
+ options: {
618
+ showContent: true,
619
+ },
620
+ })
621
+ return this.parsePositionConfig(rawData)
622
+ }
623
+
624
+ public async getOpenPositions(batchSize: number = 50, symbol: string = 'sui'): Promise<ISLPPositionInfo[]> {
625
+ let positionDynamicFields: DynamicFieldInfo[] = []
626
+ let _continue = true
627
+ let cursor = undefined
628
+ while (_continue) {
629
+ // data here will be a list of dynamic fields containing name and value
630
+ const { data, nextCursor, hasNextPage } =
631
+ await this.provider.getDynamicFields({
632
+ parentId: this.consts.sudoCore.positionsParent,
633
+ cursor,
634
+ })
635
+
636
+ positionDynamicFields = positionDynamicFields.concat(data)
637
+ _continue = hasNextPage
638
+ cursor = nextCursor
639
+ }
640
+
641
+ // Filter by symbol if provided
642
+ if (symbol && this.consts.coins[symbol]) {
643
+ const coinModule = symbol === 'sui' ? '0x2::sui::SUI' : this.consts.coins[symbol].module
644
+ positionDynamicFields = positionDynamicFields.filter(field => {
645
+ // Extract the second coin module from PositionName<coin1, coin2, direction>
646
+ const typeStr = field.name?.type
647
+ if (!typeStr) return false
648
+
649
+ const match = typeStr.match(/PositionName<([^,]+),\s*([^,]+),\s*([^>]+)>/)
650
+ if (!match) return false
651
+
652
+ const secondCoin = match[2].trim()
653
+ return secondCoin === coinModule
654
+ })
655
+ } else {
656
+ return []
657
+ }
658
+
659
+ // then we query by dynamic field names and order by time
660
+ const positionInfoList: ISLPPositionInfo[] = []
661
+
662
+ for (let i = 0; i < positionDynamicFields.length; i += batchSize) {
663
+ const batch = positionDynamicFields.slice(i, i + batchSize)
664
+
665
+ await Promise.all(
666
+ batch.map(async positionDynamicField => {
667
+ const positionRaw = await this.provider.getDynamicFieldObject({
668
+ parentId: this.consts.sudoCore.positionsParent,
669
+ name: positionDynamicField.name,
670
+ })
671
+
672
+ if (positionRaw?.data?.content) {
673
+ // @ts-ignore
674
+ if (positionRaw?.data?.content?.fields?.value?.fields?.closed) {
675
+ // skip closed positions
676
+ return
677
+ }
678
+ const positionInfo = await this.parsePositionInfo(
679
+ positionRaw,
680
+ positionDynamicField.objectId,
681
+ )
682
+ if (positionInfo) {
683
+ positionInfoList.push(positionInfo)
684
+ }
685
+ }
686
+ }),
687
+ )
688
+ }
689
+
690
+ return positionInfoList
691
+ .filter(positionInfo => !positionInfo.closed)
692
+ .sort((a, b) => (a.openTimestamp > b.openTimestamp ? 1 : -1))
693
+ }
694
+
695
+ public async getPositionCapInfoList(owner: string): Promise<ISLPPositionCapInfo[]> {
696
+ let cursor: string | undefined | null = undefined
697
+ let hasNextPage = true
698
+ const positionCapInfoList = []
699
+
700
+ while (hasNextPage) {
701
+ const positionCaps = await this.provider.getOwnedObjects({
702
+ owner,
703
+ filter: {
704
+ StructType: `${this.consts.sudoCore.package}::market::PositionCap`,
705
+ },
706
+ options: {
707
+ showType: true,
708
+ },
709
+ cursor,
710
+ })
711
+
712
+ for (const positionCap of positionCaps.data) {
713
+ if (positionCap.data?.type?.includes('PositionCap')) {
714
+ positionCapInfoList.push({
715
+ positionCapId: positionCap.data.objectId,
716
+ symbol0: positionCap.data.type.split('<')[1].split(',')[0].trim(),
717
+ symbol1: positionCap.data.type
718
+ .split('<')[1]
719
+ .split(',')[1]
720
+ .split(',')[0]
721
+ .trim(),
722
+ long: positionCap.data.type.includes('LONG'),
723
+ })
724
+ }
725
+ }
726
+
727
+ hasNextPage = positionCaps.hasNextPage
728
+ cursor = positionCaps.nextCursor
729
+ }
730
+
731
+ return positionCapInfoList
732
+ }
733
+
734
+ public async getOrderCapInfoList(owner: string): Promise<ISLPOrderCapInfo[]> {
735
+ let cursor: string | undefined | null = undefined
736
+ let hasNextPage = true
737
+ const orderCapInfoList = []
738
+
739
+ while (hasNextPage) {
740
+ const orderCaps = await this.provider.getOwnedObjects({
741
+ owner,
742
+ filter: {
743
+ StructType: `${this.consts.sudoCore.package}::market::OrderCap`,
744
+ },
745
+ options: {
746
+ showType: true,
747
+ showContent: true,
748
+ },
749
+ cursor,
750
+ })
751
+
752
+ for (const orderCap of orderCaps.data) {
753
+ if (orderCap.data?.type?.includes('OrderCap')) {
754
+ orderCapInfoList.push({
755
+ orderCapId: orderCap.data.objectId,
756
+ symbol0: orderCap.data.type.split('<')[1].split(',')[0].trim(),
757
+ symbol1: orderCap.data.type
758
+ .split('<')[1]
759
+ .split(',')[1]
760
+ .split(',')[0]
761
+ .trim(),
762
+ long: orderCap.data.type.includes('LONG'),
763
+ positionId: (orderCap.data.content as any)?.fields?.position_id,
764
+ })
765
+ }
766
+ }
767
+
768
+ hasNextPage = orderCaps.hasNextPage
769
+ cursor = orderCaps.nextCursor
770
+ }
771
+
772
+ return orderCapInfoList
773
+ }
774
+
775
+ public async hasReferral(referree: string): Promise<boolean> {
776
+ const raw = await this.getReferralData(referree)
777
+ return !raw.error
778
+ }
779
+
780
+ public async getReferralData(referree: string): Promise<any> {
781
+ const raw = await this.provider.getDynamicFieldObject({
782
+ parentId: this.consts.sudoCore.referralsParent,
783
+ name: {
784
+ type: 'address',
785
+ value: referree,
786
+ },
787
+ })
788
+ return raw
789
+ }
790
+
791
+ /**
792
+ * Gets user orders for SLP
793
+ */
794
+ public async getOrderInfoList(
795
+ orderCapInfoList: ISLPOrderCapInfo[],
796
+ owner: string
797
+ ): Promise<ISLPOrderInfo[]> {
798
+ const orderInfoList: ISLPOrderInfo[] = []
799
+ await Promise.all(
800
+ orderCapInfoList.map(async orderCapInfo => {
801
+ const orderRaw = await this.provider.getDynamicFieldObject({
802
+ parentId: this.consts.sudoCore.ordersParent,
803
+ name: {
804
+ type: `${this.consts.sudoCore.package}::market::OrderName<${orderCapInfo.symbol0
805
+ }, ${orderCapInfo.symbol1}, ${this.consts.sudoCore.package
806
+ }::market::${orderCapInfo.long ? 'LONG' : 'SHORT'}, ${this.consts.coins['sui'].module
807
+ }>`,
808
+ value: {
809
+ owner,
810
+ id: orderCapInfo.orderCapId,
811
+ position_id: {
812
+ vec: orderCapInfo.positionId ? [orderCapInfo.positionId] : [],
813
+ },
814
+ },
815
+ },
816
+ })
817
+ orderInfoList.push(
818
+ this.parseOrderInfo(orderRaw, orderCapInfo.orderCapId),
819
+ )
820
+ }),
821
+ )
822
+ return orderInfoList.sort((a, b) => (a.createdAt > b.createdAt ? 1 : -1))
823
+ }
824
+
825
+ public async getCumulativeApr() {
826
+ const refetchDate = new Date(Date.now() - 3600_000)
827
+ // fetch new every hour
828
+ if (
829
+ !aprResponse?.generatedAt ||
830
+ (aprResponse?.generatedAt &&
831
+ refetchDate > new Date(aprResponse?.generatedAt))
832
+ ) {
833
+ try {
834
+ const url = `${this.apiEndpoint}/cumulativeApr`
835
+ const res = await fetch(url, {
836
+ method: 'GET',
837
+ headers: {
838
+ 'Content-Type': 'application/json',
839
+ },
840
+ })
841
+ const data = await res.json()
842
+ aprResponse = { ...data }
843
+ return data.cumulativeApr
844
+ } catch (e) {
845
+ console.error('Failed to get cumulative APR')
846
+ }
847
+
848
+ return 0
849
+ } else {
850
+ return aprResponse.apr
851
+ }
852
+ }
853
+
854
+ // Private helper methods
855
+ private calculatePositionFundingFee(position: ISLPPositionInfo, symbol: ISLPSymbolInfo, model: ISLPFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number): number {
856
+ const accFundingRate = this.calcAccFundingFeeRate(symbol, model, price, lpSupplyAmount, timestamp, position.long);
857
+ return position.fundingFeeValue + (accFundingRate - position.lastFundingRate) * position.positionSize;
858
+ }
859
+
860
+ private calcAccFundingFeeRate(symbol: ISLPSymbolInfo, model: ISLPFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number, isLong: boolean): number {
861
+ if (symbol.lastUpdate > 0) {
862
+ const elapsed = timestamp - symbol.lastUpdate;
863
+ if (elapsed > 0) {
864
+ const deltaSize = this.calcDeltaSize(symbol, price, isLong);
865
+ const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount;
866
+ return symbol.accFundingRate + this.calcFundingFeeRate(model, pnlPerLp, elapsed);
867
+ }
868
+ }
869
+ return symbol.accFundingRate;
870
+ }
871
+
872
+ private calcDeltaSize(symbol: ISLPSymbolInfo, price: number, isLong: boolean): number {
873
+ const latestSize = symbol.openingAmount * price;
874
+ return isLong ? symbol.openingSize - latestSize : latestSize - symbol.openingSize;
875
+ }
876
+
877
+ private calcFundingFeeRate(model: ISLPFundingFeeModel, pnlPerRate: number, elapsed: number): number {
878
+ const dailyRate = Math.min(model.multiplier * Math.abs(pnlPerRate), model.max);
879
+ const secondsRate = dailyRate * elapsed / SECONDS_PER_EIGHT_HOUR;
880
+ return pnlPerRate >= 0 ? -secondsRate : secondsRate;
881
+ }
882
+
883
+ private calculatePositionReserveFee(position: ISLPPositionInfo, vault: ISLPVaultInfo, model: ISLPReservingFeeModel, timestamp: number): number {
884
+ const accReservingRate = this.calcAccReservingFeeRate(vault, model, timestamp);
885
+ return position.reservingFeeAmount + (accReservingRate - position.lastReservingRate) * position.collateralAmount;
886
+ }
887
+
888
+ private calcAccReservingFeeRate(vault: ISLPVaultInfo, model: ISLPReservingFeeModel, timestamp: number): number {
889
+ if (vault.lastUpdate > 0) {
890
+ const elapsed = timestamp - vault.lastUpdate;
891
+ if (elapsed > 0) {
892
+ const utilization = this.vaultUtilization(vault);
893
+ return vault.accReservingRate + this.calcReservingFeeRate(model, utilization, elapsed);
894
+ }
895
+ }
896
+ return vault.accReservingRate;
897
+ }
898
+
899
+ private vaultUtilization(vault: ISLPVaultInfo): number {
900
+ return vault.liquidity > 0 ? vault.reservedAmount / vault.liquidity : 0;
901
+ }
902
+
903
+ private calcReservingFeeRate(model: ISLPReservingFeeModel, utilization: number, elapsed: number): number {
904
+ return model.multiplier * utilization * elapsed / SECONDS_PER_EIGHT_HOUR
905
+ }
906
+
907
+ private parsePositionConfig(raw: any): ISLPPositionConfig {
908
+ const positionConfigFields = raw.data.content.fields.inner.fields
909
+
910
+ return {
911
+ decreaseFeeBps: parseValue(positionConfigFields.decrease_fee_bps),
912
+ liquidationBonus: parseValue(positionConfigFields.liquidation_bonus),
913
+ liquidationThreshold: parseValue(
914
+ positionConfigFields.liquidation_threshold,
915
+ ),
916
+ maxLeverage: parseValue(positionConfigFields.max_leverage),
917
+ minHoldingDuration: parseValue(positionConfigFields.min_holding_duration),
918
+ openFeeBps: parseValue(positionConfigFields.open_fee_bps),
919
+ maxReservedMultiplier: parseValue(
920
+ positionConfigFields.max_reserved_multiplier,
921
+ ),
922
+ minCollateralValue: parseValue(positionConfigFields.min_collateral_value),
923
+ }
924
+ }
925
+
926
+ private parseRebaseFeeModel(raw: any): ISLPRebaseFeeModel {
927
+ const { fields } = raw.data.content
928
+
929
+ return {
930
+ base: parseValue(fields.base),
931
+ multiplier: parseValue(fields.multiplier),
932
+ }
933
+ }
934
+
935
+ private parseFundingFeeModel(raw: any): ISLPFundingFeeModel {
936
+ const { fields } = raw.data.content
937
+
938
+ return {
939
+ multiplier: parseValue(fields.multiplier),
940
+ max: parseValue(fields.max),
941
+ }
942
+ }
943
+
944
+ private parseMarketInfo(raw: any): ISLPMarketInfo {
945
+ const content = raw.data.content.fields
946
+
947
+ return {
948
+ lpSupply: content.lp_supply.fields.value,
949
+ positionId: content.positions.fields.id.id,
950
+ vaultId: content.vaults.fields.id.id,
951
+ symbolId: content.symbols.fields.id.id,
952
+ lpSupplyWithDecimals:
953
+ content.lp_supply.fields.value / 10 ** SLP_TOKEN_DECIMALS,
954
+ }
955
+ }
956
+
957
+ private async parseVaultInfo(raw: any): Promise<ISLPVaultInfo> {
958
+ const vaultFields = raw.data.content.fields.value.fields
959
+ const reservingFeeModelAddr = vaultFields.reserving_fee_model
960
+ const reservingFeeModelRaw = await this.provider.getObject({
961
+ id: reservingFeeModelAddr,
962
+ options: {
963
+ showContent: true,
964
+ },
965
+ })
966
+ const reservingFeeModel = this.parseReservingFeeModel(reservingFeeModelRaw)
967
+
968
+ return {
969
+ liquidity: parseValue(vaultFields.liquidity),
970
+ reservedAmount: parseValue(vaultFields.reserved_amount),
971
+ unrealisedReservingFeeAmount: parseValue(
972
+ vaultFields.unrealised_reserving_fee_amount,
973
+ ),
974
+ accReservingRate: parseValue(vaultFields.acc_reserving_rate),
975
+ enabled: vaultFields.enabled,
976
+ weight: parseValue(vaultFields.weight),
977
+ lastUpdate: parseValue(vaultFields.last_update),
978
+ reservingFeeModel,
979
+ priceConfig: {
980
+ maxInterval: parseValue(vaultFields.price_config.fields.max_interval),
981
+ maxConfidence: parseValue(vaultFields.price_config.fields.max_confidence),
982
+ precision: parseValue(vaultFields.price_config.fields.precision),
983
+ feeder: vaultFields.price_config.fields.feeder,
984
+ },
985
+ }
986
+ }
987
+
988
+ private parseReservingFeeModel(raw: any): { multiplier: number } {
989
+ const content = raw.data.content.fields
990
+ return {
991
+ multiplier: parseValue(content.multiplier),
992
+ }
993
+ }
994
+
995
+ private async parseSymbolInfo(raw: any, long: boolean): Promise<ISLPSymbolInfo> {
996
+ const fields = raw.data.content.fields.value.fields
997
+ const fundingFeeModelAddr = fields.funding_fee_model
998
+ const fundingFeeModelRaw = await this.provider.getObject({
999
+ id: fundingFeeModelAddr,
1000
+ options: {
1001
+ showContent: true,
1002
+ },
1003
+ })
1004
+ const fundingFeeModel = this.parseFundingFeeModel(fundingFeeModelRaw)
1005
+
1006
+ return {
1007
+ openingSize: parseValue(fields.opening_size),
1008
+ openingAmount: parseValue(fields.opening_amount),
1009
+ accFundingRate: parseValue(fields.acc_funding_rate),
1010
+ realisedPnl: parseValue(fields.realised_pnl),
1011
+ unrealisedFundingFeeValue: parseValue(
1012
+ fields.unrealised_funding_fee_value,
1013
+ ),
1014
+ openEnabled: fields.open_enabled,
1015
+ liquidateEnabled: fields.liquidate_enabled,
1016
+ decreaseEnabled: fields.decrease_enabled,
1017
+ lastUpdate: parseValue(fields.last_update),
1018
+ fundingFeeModel,
1019
+ long,
1020
+ priceConfig: {
1021
+ maxInterval: parseValue(fields.price_config.fields.max_interval),
1022
+ maxConfidence: parseValue(fields.price_config.fields.max_confidence),
1023
+ precision: parseValue(fields.price_config.fields.precision),
1024
+ feeder: fields.price_config.fields.feeder,
1025
+ },
1026
+ }
1027
+ }
1028
+
1029
+ private async parsePositionInfo(raw: any, id_: string): Promise<ISLPPositionInfo> {
1030
+ const content = raw.data.content
1031
+ const fields = content.fields
1032
+ const positionFields = fields.value.fields
1033
+ const dataType = fields.name.type
1034
+
1035
+ const positionInfo = {
1036
+ id: id_,
1037
+ long: dataType.includes('::market::LONG'),
1038
+ owner: fields.name.fields.owner,
1039
+ version: parseInt(raw.data.version, 10),
1040
+ collateralToken: suiSymbolToSymbol(
1041
+ dataType.split('<')[1].split(',')[0].trim(),
1042
+ this.consts,
1043
+ ),
1044
+ indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
1045
+ collateralAmount: parseValue(positionFields.collateral),
1046
+ positionAmount: parseValue(positionFields.position_amount),
1047
+ reservedAmount: parseValue(positionFields.reserved),
1048
+ positionSize: parseValue(positionFields.position_size),
1049
+ lastFundingRate: parseValue(positionFields.last_funding_rate),
1050
+ lastReservingRate: parseValue(positionFields.last_reserving_rate),
1051
+ reservingFeeAmount: parseValue(positionFields.reserving_fee_amount),
1052
+ fundingFeeValue: parseValue(positionFields.funding_fee_value),
1053
+ closed: positionFields.closed,
1054
+ openTimestamp: parseValue(positionFields.open_timestamp),
1055
+ protocol: 'sudo',
1056
+ }
1057
+
1058
+ if (!positionFields.closed) {
1059
+ try {
1060
+ positionInfo.reservingFeeAmount = this.calculatePositionReserveFee(
1061
+ positionInfo,
1062
+ await this.getVaultInfo(positionInfo.collateralToken),
1063
+ (await this.getVaultInfo(positionInfo.collateralToken)).reservingFeeModel,
1064
+ Date.now() / 1000
1065
+ )
1066
+ positionInfo.fundingFeeValue = this.calculatePositionFundingFee(
1067
+ positionInfo,
1068
+ await this.getSymbolInfo(positionInfo.indexToken, positionInfo.long),
1069
+ (await this.getSymbolInfo(positionInfo.indexToken, positionInfo.long)).fundingFeeModel,
1070
+ (await this.getOraclePrice(positionInfo.indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked(),
1071
+ (await this.getMarketInfo()).lpSupplyWithDecimals,
1072
+ Date.now() / 1000
1073
+ )
1074
+ } catch (e) {
1075
+ console.error(e)
1076
+ positionInfo.reservingFeeAmount = 0
1077
+ positionInfo.fundingFeeValue = 0
1078
+ }
1079
+ }
1080
+
1081
+ return positionInfo
1082
+ }
1083
+
1084
+ private parseOrderInfo(raw: any, capId: string): ISLPOrderInfo {
1085
+ let content = raw.data.content
1086
+ let fields = content.fields.value.fields
1087
+
1088
+ // Extract tokens from dataType
1089
+ let dataType = content.type
1090
+
1091
+ const orderType = content.fields.value.type.includes('OpenPositionOrder')
1092
+ ? 'OPEN_POSITION'
1093
+ : 'DECREASE_POSITION'
1094
+
1095
+ let ret: ISLPOrderInfo = {
1096
+ id: content.fields.id.id,
1097
+ capId,
1098
+ executed: fields.executed,
1099
+ owner: content.fields.name.fields.owner,
1100
+ collateralToken: suiSymbolToSymbol(
1101
+ dataType.split('<')[2].split(',')[0].trim(),
1102
+ this.consts,
1103
+ ),
1104
+ indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
1105
+ feeToken: suiSymbolToSymbol(
1106
+ dataType.split(',')[3].split('>')[0].trim(),
1107
+ this.consts,
1108
+ ),
1109
+ indexPrice: decimalToObject(fields.limited_index_price.fields),
1110
+ collateralPriceThreshold: parseValue(fields.collateral_price_threshold),
1111
+ feeAmount: BigInt(fields.fee),
1112
+ long: dataType.includes('::market::LONG'),
1113
+ orderType,
1114
+ createdAt: parseValue(fields.created_at),
1115
+ }
1116
+
1117
+ if (orderType === 'OPEN_POSITION') {
1118
+ ret.openOrder = {
1119
+ reserveAmount: BigInt(fields.reserve_amount),
1120
+ collateralAmount: BigInt(fields.collateral),
1121
+ openAmount: BigInt(fields.open_amount),
1122
+ }
1123
+ } else {
1124
+ ret.decreaseOrder = {
1125
+ decreaseAmount: BigInt(fields.decrease_amount),
1126
+ takeProfit: fields.take_profit,
1127
+ }
1128
+ }
1129
+
1130
+ return ret
1131
+ }
1132
+
1133
+ private parseCredential(raw: any, pool: ISLPStakePool): ISLPCredential {
1134
+ const stakedAmount = BigInt(raw.data.content.fields.stake)
1135
+ const accRewardPerShare = BigInt(
1136
+ raw.data.content.fields.acc_reward_per_share,
1137
+ )
1138
+ return {
1139
+ id: raw.data.objectId,
1140
+ lockUntil: parseValue(raw.data.content.fields.lock_until),
1141
+ amount: stakedAmount,
1142
+ accRewardPerShare,
1143
+ claimable:
1144
+ ((pool.accRewardPerShare - accRewardPerShare) * stakedAmount) /
1145
+ BigInt(1e18),
1146
+ }
1147
+ }
1148
+
1149
+ private parseStakePool(raw: any): ISLPStakePool {
1150
+ const content = raw.data.content.fields
1151
+ const pool = {
1152
+ id: content.id.id,
1153
+ enabled: content.enabled,
1154
+ lastUpdatedTime: parseValue(content.last_updated_time),
1155
+ stakedAmount: BigInt(content.staked_amount),
1156
+ reward: BigInt(content.reward),
1157
+ startTime: parseValue(content.start_time),
1158
+ endTime: parseValue(content.end_time),
1159
+ accRewardPerShare: BigInt(content.acc_reward_per_share),
1160
+ lockDuration: parseValue(content.lock_duration),
1161
+ }
1162
+ this.refreshPool(pool, Math.floor(Date.now() / 1000))
1163
+ return pool
1164
+ }
1165
+
1166
+ private refreshPool(pool: ISLPStakePool, timestamp: number): void {
1167
+ if (timestamp === pool.lastUpdatedTime || timestamp < pool.startTime) {
1168
+ return
1169
+ }
1170
+ if (
1171
+ pool.lastUpdatedTime === pool.endTime ||
1172
+ pool.stakedAmount === BigInt(0)
1173
+ ) {
1174
+ return
1175
+ }
1176
+ if (timestamp > pool.endTime) {
1177
+ timestamp = pool.endTime
1178
+ }
1179
+
1180
+ const rewardAmount =
1181
+ (pool.reward * BigInt(timestamp - pool.lastUpdatedTime)) /
1182
+ BigInt(pool.endTime - pool.lastUpdatedTime)
1183
+
1184
+ pool.lastUpdatedTime = timestamp
1185
+ const rewardPerShare = (rewardAmount * BigInt(1e18)) / pool.stakedAmount
1186
+ pool.accRewardPerShare += rewardPerShare
1187
+ }
1188
+ }