@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
package/src/data.ts ADDED
@@ -0,0 +1,1279 @@
1
+ /* eslint-disable class-methods-use-this */
2
+
3
+ import type { DynamicFieldInfo, SuiClient } from '@mysten/sui/client'
4
+ import type { Transaction } from '@mysten/sui/transactions'
5
+ import { SUI_CLOCK_OBJECT_ID } from '@mysten/sui/utils'
6
+
7
+ import type { Network } from './consts'
8
+ import { SECONDS_PER_EIGHT_HOUR, ZLP_TOKEN_DECIMALS } from './consts'
9
+ import { OracleAPI } from './oracle'
10
+ import { createJsonRpcProvider, joinSymbol, parseSymbolKey, parseValue, suiSymbolToSymbol } from './utils'
11
+
12
+ export interface IMarketInfo {
13
+ lpSupply: string
14
+ positionId: string
15
+ vaultId: string
16
+ symbolId: string
17
+ referralId: string
18
+ orderId: string
19
+ rebaseFeeModel: string
20
+ lpSupplyWithDecimals: number
21
+ };
22
+
23
+ export interface IRebaseFeeModel {
24
+ base: number
25
+ multiplier: number
26
+ }
27
+
28
+ export interface IReservingFeeModel {
29
+ multiplier: number
30
+ }
31
+
32
+ export interface IFundingFeeModel {
33
+ multiplier: number
34
+ max: number
35
+ }
36
+
37
+ export interface IVaultInfo {
38
+ liquidity: number
39
+ reservedAmount: number
40
+ unrealisedReservingFeeAmount: number
41
+ accReservingRate: number
42
+ enabled: boolean
43
+ weight: number
44
+ lastUpdate: number
45
+ reservingFeeModel: IReservingFeeModel
46
+ priceConfig: {
47
+ maxInterval: number
48
+ maxConfidence: number
49
+ precision: number
50
+ feeder: string
51
+ }
52
+ }
53
+
54
+ export interface ISymbolInfo {
55
+ openingSize: number
56
+ openingAmount: number
57
+ accFundingRate: number
58
+ realisedPnl: number
59
+ unrealisedFundingFeeValue: number
60
+ openEnabled: boolean
61
+ liquidateEnabled: boolean
62
+ decreaseEnabled: boolean
63
+ lastUpdate: number
64
+ fundingFeeModel: IFundingFeeModel
65
+ long: boolean
66
+ priceConfig: {
67
+ maxInterval: number
68
+ maxConfidence: number
69
+ precision: number
70
+ feeder: string
71
+ }
72
+ }
73
+
74
+ export interface IPositionInfo {
75
+ id: string
76
+ long: boolean
77
+ owner: string
78
+ version: number
79
+
80
+ collateralToken: string
81
+ indexToken: string
82
+
83
+ collateralAmount: number
84
+ positionAmount: number
85
+ reservedAmount: number
86
+
87
+ positionSize: number
88
+ lastFundingRate: number
89
+ lastReservingRate: number
90
+
91
+ reservingFeeAmount: number
92
+ fundingFeeValue: number
93
+
94
+ closed: boolean
95
+
96
+ openTimestamp: number
97
+ }
98
+
99
+ export interface IPositionCapInfo {
100
+ positionCapId: string
101
+ symbol0: string
102
+ symbol1: string
103
+ long: boolean
104
+ }
105
+
106
+ export interface IOrderCapInfo {
107
+ orderCapId: string
108
+ symbol0: string
109
+ symbol1: string
110
+ long: boolean
111
+ positionId: string | null
112
+ }
113
+
114
+ export interface IOrderInfo {
115
+ id: string
116
+ capId: string
117
+ executed: boolean
118
+ owner: string
119
+ collateralToken: string
120
+ indexToken: string
121
+ feeToken: string
122
+ collateralPriceThreshold: number
123
+ feeAmount: bigint
124
+ long: boolean
125
+ indexPrice: number
126
+ openOrder?: {
127
+ reserveAmount: bigint
128
+ collateralAmount: bigint
129
+ openAmount: bigint
130
+ }
131
+ decreaseOrder?: {
132
+ decreaseAmount: bigint
133
+ takeProfit: boolean
134
+ }
135
+ orderType: 'OPEN_POSITION' | 'DECREASE_POSITION'
136
+ createdAt: number
137
+ v11Order: boolean
138
+ }
139
+
140
+ export interface IMarketValuationInfo {
141
+ marketCap: number
142
+ zlpPrice: number
143
+ zlpSupply: number
144
+ apr?: number
145
+ }
146
+
147
+ export interface IPositionConfig {
148
+ decreaseFeeBps: number
149
+ liquidationBonus: number
150
+ liquidationThreshold: number
151
+ maxLeverage: number
152
+ minHoldingDuration: number
153
+ openFeeBps: number
154
+ maxReservedMultiplier: number
155
+ minCollateralValue: number
156
+ }
157
+
158
+ export interface IHistory {
159
+ owner: string
160
+ txid: string
161
+ id: string
162
+ created: number
163
+ eventName: string
164
+ indexToken: string
165
+ direction: string
166
+ collateralAmount: number
167
+ collateralPrice: number
168
+ indexPrice: number
169
+ pnl: number
170
+ positionId: string
171
+ volume: number
172
+ fee: number
173
+ network: string
174
+ }
175
+
176
+ export interface IStaked {
177
+ credentials: ICredential[]
178
+ amount: bigint
179
+ claimable: bigint
180
+ }
181
+
182
+ export interface ICredential {
183
+ id: string
184
+ lockUntil: number
185
+ accRewardPerShare: bigint
186
+ amount: bigint
187
+ claimable: bigint
188
+ }
189
+
190
+ export interface IStakePool {
191
+ id: string
192
+ enabled: boolean
193
+ lastUpdatedTime: number
194
+ stakedAmount: bigint
195
+ reward: bigint
196
+ startTime: number
197
+ endTime: number
198
+ accRewardPerShare: bigint
199
+ lockDuration: number
200
+ }
201
+
202
+ export interface IMarketValidationResult {
203
+ handledVaults: Record<string, {
204
+ weight: number
205
+ value: number
206
+ }>
207
+ totalWeight: number
208
+ totalVaultsValue: number
209
+ marketValue: number
210
+ }
211
+
212
+ export class DataAPI extends OracleAPI {
213
+ provider: SuiClient
214
+ apiEndpoint: string
215
+
216
+ private vaultInfoCache: Record<string, IVaultInfo> = {}
217
+
218
+ private symbolInfoCache: Record<string, ISymbolInfo> = {}
219
+
220
+ private marketInfoCache: IMarketInfo | null = null
221
+ private positionConfigCache: Record<string, IPositionConfig> = {}
222
+
223
+ private rebaseFeeModelCache: IRebaseFeeModel | null = null
224
+ private lastUpdate = 0
225
+
226
+ constructor(
227
+ network: Network,
228
+ provider: SuiClient | null,
229
+ apiEndpoint: string,
230
+ connectionURL: string,
231
+ ) {
232
+ super(network, provider, connectionURL)
233
+ this.provider = provider || createJsonRpcProvider(network)
234
+ this.apiEndpoint = apiEndpoint
235
+ }
236
+
237
+ validateCache = () => {
238
+ super.validateCache()
239
+ if (this.lastUpdate + 1000 * 60 * 3 < Date.now()) {
240
+ this.lastUpdate = Date.now()
241
+ this.vaultInfoCache = {}
242
+ this.symbolInfoCache = {}
243
+ this.marketInfoCache = null
244
+ this.positionConfigCache = {}
245
+ this.rebaseFeeModelCache = null
246
+ }
247
+ }
248
+
249
+ valuateVaults = (tx: Transaction) => {
250
+ const vaultsValuation = tx.moveCall({
251
+ target: `${this.consts.zoCore.upgradedPackage}::market::create_vaults_valuation`,
252
+ typeArguments: [`${this.consts.zoCore.package}::zlp::ZLP`],
253
+ arguments: [
254
+ tx.object(SUI_CLOCK_OBJECT_ID),
255
+ tx.object(this.consts.zoCore.market),
256
+ ],
257
+ })
258
+ for (const key of Object.keys(this.consts.zoCore.vaults)) {
259
+ const vault = this.consts.zoCore.vaults[key]
260
+ tx.moveCall({
261
+ // todo
262
+ target: `${this.consts.zoCore.upgradedPackage}::market::valuate_vault`,
263
+ typeArguments: [
264
+ `${this.consts.zoCore.package}::zlp::ZLP`,
265
+ this.consts.coins[key].module,
266
+ ],
267
+ arguments: [
268
+ tx.object(this.consts.zoCore.market),
269
+ tx.object(vault.reservingFeeModel),
270
+ tx.object(this.consts.pythFeeder.feeder[key]),
271
+ vaultsValuation,
272
+ ],
273
+ })
274
+ }
275
+ return vaultsValuation
276
+ }
277
+
278
+ valuateSymbols = (tx: Transaction) => {
279
+ const symbolsValuation = tx.moveCall({
280
+ target: `${this.consts.zoCore.upgradedPackage}::market::create_symbols_valuation`,
281
+ typeArguments: [`${this.consts.zoCore.package}::zlp::ZLP`],
282
+ arguments: [
283
+ tx.object(SUI_CLOCK_OBJECT_ID),
284
+ tx.object(this.consts.zoCore.market),
285
+ ],
286
+ })
287
+ for (const key of Object.keys(this.consts.zoCore.symbols)) {
288
+ const [direction, token] = parseSymbolKey(key)
289
+ const symbol = this.consts.zoCore.symbols[key]
290
+ tx.moveCall({
291
+ target: `${this.consts.zoCore.upgradedPackage}::market::valuate_symbol`,
292
+ typeArguments: [
293
+ `${this.consts.zoCore.package}::zlp::ZLP`,
294
+ this.consts.coins[token].module,
295
+ `${this.consts.zoCore.package}::market::${direction.toUpperCase()}`,
296
+ ],
297
+ arguments: [
298
+ tx.object(this.consts.zoCore.market),
299
+ tx.object(symbol.fundingFeeModel),
300
+ tx.object(this.consts.pythFeeder.feeder[token]),
301
+ symbolsValuation,
302
+ ],
303
+ })
304
+ }
305
+ return symbolsValuation
306
+ }
307
+
308
+ valuate = (tx: Transaction) => {
309
+ const vaultsValuation = this.valuateVaults(tx)
310
+ const symbolsValuation = this.valuateSymbols(tx)
311
+
312
+ return {
313
+ vaultsValuation,
314
+ symbolsValuation,
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Validates the market valuation by calling the validate_market_valuation Move function
320
+ * @param tx Transaction object
321
+ * @returns An object containing handled vaults, total weight, total vaults value, and market value
322
+ */
323
+ validateMarketValuation = (tx: Transaction) => {
324
+ // First, create the valuations using existing methods
325
+ const vaultsValuation = this.valuateVaults(tx)
326
+ const symbolsValuation = this.valuateSymbols(tx)
327
+
328
+ // Call the validate_market_valuation Move function
329
+ const result = tx.moveCall({
330
+ target: `${this.consts.zoCore.upgradedPackage}::market::validate_market_valuation`,
331
+ typeArguments: [`${this.consts.zoCore.package}::zlp::ZLP`],
332
+ arguments: [
333
+ tx.object(this.consts.zoCore.market),
334
+ vaultsValuation,
335
+ symbolsValuation,
336
+ ],
337
+ })
338
+
339
+ return result
340
+ }
341
+
342
+ #parseMarketInfo(raw: any): IMarketInfo {
343
+ const content = raw.data.content.fields
344
+
345
+ return {
346
+ lpSupply: content.lp_supply.fields.value,
347
+ positionId: content.positions.fields.id.id,
348
+ vaultId: content.vaults.fields.id.id,
349
+ symbolId: content.symbols.fields.id.id,
350
+ referralId: content.referrals.fields.id.id,
351
+ orderId: content.orders.fields.id.id,
352
+ rebaseFeeModel: content.rebase_fee_model,
353
+ lpSupplyWithDecimals: content.lp_supply.fields.value / (10 ** ZLP_TOKEN_DECIMALS),
354
+ }
355
+ }
356
+
357
+ #parseVaultInfo = async (raw: any): Promise<IVaultInfo> => {
358
+ const vaultFields = raw.data.content.fields.value.fields
359
+ const reservingFeeModelAddr = vaultFields.reserving_fee_model
360
+ const reservingFeeModelRaw = await this.provider.getObject({
361
+ id: reservingFeeModelAddr,
362
+ options: {
363
+ showContent: true,
364
+ },
365
+ })
366
+ const reservingFeeModel = this.#parseReservingFeeModel(reservingFeeModelRaw)
367
+
368
+ return {
369
+ liquidity: parseValue(vaultFields.liquidity),
370
+ reservedAmount: parseValue(vaultFields.reserved_amount),
371
+ unrealisedReservingFeeAmount: parseValue(
372
+ vaultFields.unrealised_reserving_fee_amount,
373
+ ),
374
+ accReservingRate: parseValue(vaultFields.acc_reserving_rate),
375
+ enabled: vaultFields.enabled,
376
+ weight: parseValue(vaultFields.weight),
377
+ lastUpdate: parseValue(vaultFields.last_update),
378
+ reservingFeeModel,
379
+ priceConfig: {
380
+ maxInterval: parseValue(vaultFields.price_config.fields.max_interval),
381
+ maxConfidence: parseValue(vaultFields.price_config.fields.max_confidence),
382
+ precision: parseValue(vaultFields.price_config.fields.precision),
383
+ feeder: vaultFields.price_config.fields.feeder,
384
+ },
385
+ }
386
+ }
387
+
388
+ #parseSymbolInfo = async (raw: any, long: boolean): Promise<ISymbolInfo> => {
389
+ const { fields } = raw.data.content.fields.value
390
+ const fundingFeeModelAddr = fields.funding_fee_model
391
+ const fundingFeeModelRaw = await this.provider.getObject({
392
+ id: fundingFeeModelAddr,
393
+ options: {
394
+ showContent: true,
395
+ },
396
+ })
397
+ const fundingFeeModel = this.#parseFundingFeeModel(fundingFeeModelRaw)
398
+
399
+ return {
400
+ openingSize: parseValue(fields.opening_size),
401
+ openingAmount: parseValue(fields.opening_amount),
402
+ accFundingRate: parseValue(fields.acc_funding_rate),
403
+ realisedPnl: parseValue(fields.realised_pnl),
404
+ unrealisedFundingFeeValue: parseValue(fields.unrealised_funding_fee_value),
405
+ openEnabled: fields.open_enabled,
406
+ liquidateEnabled: fields.liquidate_enabled,
407
+ decreaseEnabled: fields.decrease_enabled,
408
+ lastUpdate: parseValue(fields.last_update),
409
+ fundingFeeModel,
410
+ long,
411
+ priceConfig: {
412
+ maxInterval: parseValue(fields.price_config.fields.max_interval),
413
+ maxConfidence: parseValue(fields.price_config.fields.max_confidence),
414
+ precision: parseValue(fields.price_config.fields.precision),
415
+ feeder: fields.price_config.fields.feeder,
416
+ },
417
+ }
418
+ }
419
+
420
+ #parseRebaseFeeModel(raw: any): IRebaseFeeModel {
421
+ const { fields } = raw.data.content
422
+
423
+ return {
424
+ base: parseValue(fields.base),
425
+ multiplier: parseValue(fields.multiplier),
426
+ }
427
+ }
428
+
429
+ #parseReservingFeeModel(raw: any): IReservingFeeModel {
430
+ const { fields } = raw.data.content
431
+
432
+ return {
433
+ multiplier: parseValue(fields.multiplier),
434
+ }
435
+ }
436
+
437
+ #parseFundingFeeModel(raw: any): IFundingFeeModel {
438
+ const { fields } = raw.data.content
439
+
440
+ return {
441
+ multiplier: parseValue(fields.multiplier),
442
+ max: parseValue(fields.max),
443
+ }
444
+ }
445
+
446
+ #parsePositionConfig(raw: any): IPositionConfig {
447
+ const positionConfigFields = raw.data.content.fields.inner.fields
448
+
449
+ return {
450
+ decreaseFeeBps: parseValue(positionConfigFields.decrease_fee_bps),
451
+ liquidationBonus: parseValue(positionConfigFields.liquidation_bonus),
452
+ liquidationThreshold: parseValue(positionConfigFields.liquidation_threshold),
453
+ maxLeverage: parseValue(positionConfigFields.max_leverage),
454
+ minHoldingDuration: parseValue(positionConfigFields.min_holding_duration),
455
+ openFeeBps: parseValue(positionConfigFields.open_fee_bps),
456
+ maxReservedMultiplier: parseValue(positionConfigFields.max_reserved_multiplier),
457
+ minCollateralValue: parseValue(positionConfigFields.min_collateral_value),
458
+ }
459
+ }
460
+
461
+ async #parsePositionInfo(raw: any, id_: string): Promise<IPositionInfo> {
462
+ const { content } = raw.data
463
+ const { fields } = content
464
+ const positionFields = fields.value.fields
465
+ const dataType = fields.name.type
466
+
467
+ const positionInfo = {
468
+ id: id_,
469
+ long: dataType.includes('::market::LONG'),
470
+ owner: fields.name.fields.owner,
471
+ version: Number.parseInt(raw.data.version, 10),
472
+ collateralToken: suiSymbolToSymbol(dataType.split('<')[1].split(',')[0].trim(), this.consts),
473
+ indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
474
+ collateralAmount: parseValue(positionFields.collateral),
475
+ positionAmount: parseValue(positionFields.position_amount),
476
+ reservedAmount: parseValue(positionFields.reserved),
477
+ positionSize: parseValue(positionFields.position_size),
478
+ lastFundingRate: parseValue(positionFields.last_funding_rate),
479
+ lastReservingRate: parseValue(positionFields.last_reserving_rate),
480
+ reservingFeeAmount: parseValue(positionFields.reserving_fee_amount),
481
+ fundingFeeValue: parseValue(positionFields.funding_fee_value),
482
+ closed: positionFields.closed,
483
+ openTimestamp: parseValue(positionFields.open_timestamp),
484
+ }
485
+
486
+ positionInfo.reservingFeeAmount = this.#calculatePositionReserveFee(positionInfo, await this.getVaultInfo(positionInfo.collateralToken), (await this.getVaultInfo(positionInfo.collateralToken)).reservingFeeModel, Date.now() / 1000)
487
+ positionInfo.fundingFeeValue = this.#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)
488
+
489
+ return positionInfo
490
+ }
491
+
492
+ #parseOrderInfo(raw: any, capId: string): IOrderInfo {
493
+ const { content } = raw.data
494
+ const { fields } = content.fields.value
495
+
496
+ // Extract tokens from dataType
497
+ const dataType = content.type
498
+
499
+ const orderType = content.fields.value.type.includes('OpenPositionOrder') ? 'OPEN_POSITION' : 'DECREASE_POSITION'
500
+
501
+ const ret: IOrderInfo = {
502
+ id: content.fields.id.id,
503
+ capId,
504
+ executed: fields.executed,
505
+ owner: content.fields.name.fields.owner,
506
+ collateralToken: suiSymbolToSymbol(dataType.split('<')[2].split(',')[0].trim(), this.consts),
507
+ indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
508
+ feeToken: suiSymbolToSymbol(dataType.split(',')[3].split('>')[0].trim(), this.consts),
509
+ indexPrice: parseValue(fields.limited_index_price.fields.price || fields.limited_index_price),
510
+ collateralPriceThreshold: parseValue(fields.collateral_price_threshold),
511
+ feeAmount: BigInt(fields.fee),
512
+ long: dataType.includes('::market::LONG'),
513
+ orderType,
514
+ createdAt: parseValue(fields.created_at),
515
+ v11Order: !fields.limited_index_price.fields.price,
516
+ }
517
+
518
+ if (orderType === 'OPEN_POSITION') {
519
+ ret.openOrder = {
520
+ reserveAmount: BigInt(fields.reserve_amount),
521
+ collateralAmount: BigInt(fields.collateral),
522
+ openAmount: BigInt(fields.open_amount),
523
+ }
524
+ }
525
+ else {
526
+ ret.decreaseOrder = {
527
+ decreaseAmount: BigInt(fields.decrease_amount),
528
+ takeProfit: fields.take_profit,
529
+ }
530
+ }
531
+
532
+ return ret
533
+ }
534
+
535
+ #calcRebaseFeeRate(model: IRebaseFeeModel, increase: boolean, ratio: number, targetRatio: number): number {
536
+ if ((increase && ratio <= targetRatio) || (!increase && ratio >= targetRatio)) {
537
+ return model.base
538
+ }
539
+ return model.base + model.multiplier * Math.abs(ratio - targetRatio)
540
+ }
541
+
542
+ #calcReservingFeeRate(model: IReservingFeeModel, utilization: number, elapsed: number): number {
543
+ return model.multiplier * utilization * elapsed / SECONDS_PER_EIGHT_HOUR
544
+ }
545
+
546
+ #calcFundingFeeRate(model: IFundingFeeModel, pnlPerRate: number, elapsed: number): number {
547
+ const dailyRate = Math.min(model.multiplier * Math.abs(pnlPerRate), model.max)
548
+ const secondsRate = dailyRate * elapsed / SECONDS_PER_EIGHT_HOUR
549
+ return pnlPerRate >= 0 ? -secondsRate : secondsRate
550
+ }
551
+
552
+ #vaultUtilization(vault: IVaultInfo): number {
553
+ const supplyAmount = vault.liquidity + vault.reservedAmount + vault.unrealisedReservingFeeAmount
554
+ if (supplyAmount === 0) {
555
+ return 0
556
+ }
557
+ return vault.reservedAmount / supplyAmount
558
+ }
559
+
560
+ #calcAccReservingFeeRate(vault: IVaultInfo, model: IReservingFeeModel, timestamp: number): number {
561
+ if (vault.lastUpdate > 0) {
562
+ const elapsed = timestamp - vault.lastUpdate
563
+ if (elapsed > 0) {
564
+ const utilization = this.#vaultUtilization(vault)
565
+ return vault.accReservingRate + this.#calcReservingFeeRate(model, utilization, elapsed)
566
+ }
567
+ }
568
+ return vault.accReservingRate
569
+ }
570
+
571
+ #calcDeltaSize(symbol: ISymbolInfo, price: number): number {
572
+ const latestSize = symbol.openingAmount / symbol.priceConfig.precision * price
573
+ return symbol.long ? symbol.openingSize - latestSize : latestSize - symbol.openingSize
574
+ }
575
+
576
+ #calcAccFundingFeeRate(symbol: ISymbolInfo, model: IFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number): number {
577
+ if (symbol.lastUpdate > 0) {
578
+ const elapsed = timestamp - symbol.lastUpdate
579
+ if (elapsed > 0) {
580
+ const deltaSize = this.#calcDeltaSize(symbol, price)
581
+ const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount
582
+ return symbol.accFundingRate + this.#calcFundingFeeRate(model, pnlPerLp, elapsed)
583
+ }
584
+ }
585
+ return symbol.accFundingRate
586
+ }
587
+
588
+ #calculateSymbolFundingFee(symbol: ISymbolInfo, model: IFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number): number {
589
+ const accFundingRate = this.#calcAccFundingFeeRate(symbol, model, price, lpSupplyAmount, timestamp)
590
+ return symbol.unrealisedFundingFeeValue + (accFundingRate - symbol.accFundingRate) * symbol.openingSize
591
+ }
592
+
593
+ #calculatePositionFundingFee(position: IPositionInfo, symbol: ISymbolInfo, model: IFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number): number {
594
+ const accFundingRate = this.#calcAccFundingFeeRate(symbol, model, price, lpSupplyAmount, timestamp)
595
+ return position.fundingFeeValue + (accFundingRate - position.lastFundingRate) * position.positionSize
596
+ }
597
+
598
+ #calculateVaultReservingFee(vault: IVaultInfo, model: IReservingFeeModel, timestamp: number): number {
599
+ const accReservingRate = this.#calcAccReservingFeeRate(vault, model, timestamp)
600
+ return vault.unrealisedReservingFeeAmount + (accReservingRate - vault.accReservingRate) * vault.reservedAmount
601
+ }
602
+
603
+ #calculatePositionReserveFee(position: IPositionInfo, vault: IVaultInfo, model: IReservingFeeModel, timestamp: number): number {
604
+ const accReservingRate = this.#calcAccReservingFeeRate(vault, model, timestamp)
605
+ return position.reservingFeeAmount + (accReservingRate - position.lastReservingRate) * position.collateralAmount
606
+ }
607
+
608
+ public async getPastFee(days = 7) {
609
+ // FIXME: Use Sentio data
610
+ const url = `${this.apiEndpoint}/histories/proxy/fee?days=${days}`
611
+ const res = await fetch(url, {
612
+ method: 'GET',
613
+ headers: {
614
+ 'Content-Type': 'application/json',
615
+ },
616
+ })
617
+ return Number.parseFloat(await res.text() || '0')
618
+ }
619
+
620
+ public async valuateMarket(): Promise<IMarketValuationInfo> {
621
+ const marketInfo = await this.getMarketInfo()
622
+ const days = 7
623
+ const fee = await this.getPastFee(days)
624
+ let zlpPrice = 0
625
+ let value = 0
626
+
627
+ const vaultPromises = Object.keys(this.consts.zoCore.vaults).map(async (vault) => {
628
+ const vaultInfo = await this.getVaultInfo(vault)
629
+ const reservingFeeDelta = this.#calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000)
630
+ return (reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount) * (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[vault].decimals)
631
+ })
632
+
633
+ const symbolPromises = Object.keys(this.consts.zoCore.symbols).map(async (symbol) => {
634
+ const [direction, tokenId] = parseSymbolKey(symbol)
635
+ const symbolInfo = await this.getSymbolInfo(tokenId, direction === 'long')
636
+ const deltaSize = this.#calcDeltaSize(symbolInfo, (await this.getOraclePrice(tokenId)).getPriceUnchecked().getPriceAsNumberUnchecked())
637
+ const fundingFeeDelta = this.#calculateSymbolFundingFee(symbolInfo, symbolInfo.fundingFeeModel, (await this.getOraclePrice(tokenId)).getPriceUnchecked().getPriceAsNumberUnchecked(), marketInfo.lpSupplyWithDecimals, Date.now() / 1000)
638
+ return fundingFeeDelta + deltaSize
639
+ })
640
+
641
+ const [vaultValues, symbolValues] = await Promise.all([Promise.all(vaultPromises), Promise.all(symbolPromises)])
642
+
643
+ value = vaultValues.reduce((acc, curr) => acc + curr, 0)
644
+ value += symbolValues.reduce((acc, curr) => acc + curr, 0)
645
+
646
+ zlpPrice = value / marketInfo.lpSupplyWithDecimals
647
+
648
+ return {
649
+ marketCap: value,
650
+ zlpPrice,
651
+ zlpSupply: marketInfo.lpSupplyWithDecimals,
652
+ apr: (fee / value) * 365 / days,
653
+ }
654
+ }
655
+
656
+ public async getMarketInfo() {
657
+ this.validateCache()
658
+ if (this.marketInfoCache) {
659
+ return this.marketInfoCache
660
+ }
661
+ const rawData = await this.provider.getObject({
662
+ id: this.consts.zoCore.market,
663
+ options: {
664
+ showContent: true,
665
+ },
666
+ })
667
+ return this.#parseMarketInfo(rawData)
668
+ }
669
+
670
+ public async getVaultInfo(vaultToken: string) {
671
+ this.validateCache()
672
+ if (this.vaultInfoCache[vaultToken]) {
673
+ return this.vaultInfoCache[vaultToken]
674
+ }
675
+
676
+ const rawData = await this.provider.getDynamicFieldObject({
677
+ parentId: this.consts.zoCore.vaultsParent,
678
+ name: {
679
+ type: `${this.consts.zoCore.package}::market::VaultName<${this.consts.coins[vaultToken].module}>`,
680
+ value: { dummy_field: false },
681
+ },
682
+ })
683
+ return await this.#parseVaultInfo(rawData)
684
+ }
685
+
686
+ public async getSymbolInfo(indexToken: string, long: boolean) {
687
+ this.validateCache()
688
+ const symbol = joinSymbol(long ? 'long' : 'short', indexToken)
689
+ if (this.symbolInfoCache[symbol]) {
690
+ return this.symbolInfoCache[symbol]
691
+ }
692
+ const rawData = await this.provider.getDynamicFieldObject({
693
+ parentId: this.consts.zoCore.symbolsParent,
694
+ name: {
695
+ type: `${this.consts.zoCore.package}::market::SymbolName<${this.consts.coins[indexToken].module}, ${this.consts.zoCore.package}::market::${long ? 'LONG' : 'SHORT'}>`,
696
+ value: { dummy_field: false },
697
+ },
698
+ })
699
+ return await this.#parseSymbolInfo(rawData, long)
700
+ }
701
+
702
+ public async getPositionConfig(indexToken: string, long: boolean) {
703
+ this.validateCache()
704
+ const symbol = joinSymbol(long ? 'long' : 'short', indexToken)
705
+ if (this.positionConfigCache[symbol]) {
706
+ return this.positionConfigCache[symbol]
707
+ }
708
+ const rawData = await this.provider.getObject({
709
+ id: this.consts.zoCore.symbols[symbol].positionConfig,
710
+ options: {
711
+ showContent: true,
712
+ },
713
+ })
714
+ return this.#parsePositionConfig(rawData)
715
+ }
716
+
717
+ public async getPositionCapInfoList(owner: string): Promise<IPositionCapInfo[]> {
718
+ const positionCapInfoList: IPositionCapInfo[] = []
719
+ let cursor: string | undefined | null
720
+ let hasNextPage = true
721
+
722
+ while (hasNextPage) {
723
+ /* eslint-disable-next-line no-await-in-loop -- pagination requires sequential fetches */
724
+ const positionCaps = await this.provider.getOwnedObjects({
725
+ owner,
726
+ filter: {
727
+ MoveModule: {
728
+ package: this.consts.zoCore.package,
729
+ module: 'market',
730
+ },
731
+ },
732
+ options: {
733
+ showType: true,
734
+ },
735
+ cursor,
736
+ })
737
+
738
+ for (const positionCap of positionCaps.data) {
739
+ if (positionCap.data?.type?.includes('PositionCap')) {
740
+ positionCapInfoList.push({
741
+ positionCapId: positionCap.data.objectId,
742
+ symbol0: positionCap.data.type.split('<')[1].split(',')[0].trim(),
743
+ symbol1: positionCap.data.type.split('<')[1].split(',')[1].split(',')[0].trim(),
744
+ long: positionCap.data.type.includes('LONG'),
745
+ })
746
+ }
747
+ }
748
+
749
+ // If we don't want to fetch all pages or there are no more pages, break the loop
750
+ hasNextPage = positionCaps.hasNextPage
751
+ cursor = positionCaps.nextCursor
752
+ }
753
+
754
+ return positionCapInfoList
755
+ }
756
+
757
+ public async getPositionInfoList(positionCapInfoList: IPositionCapInfo[], owner: string, batchSize = 10) {
758
+ const positionInfoList: IPositionInfo[] = []
759
+
760
+ // Process in batches of 10
761
+ for (let i = 0; i < positionCapInfoList.length; i += batchSize) {
762
+ const batch = positionCapInfoList.slice(i, i + batchSize)
763
+
764
+ /* eslint-disable-next-line no-await-in-loop -- batch processed in parallel via Promise.all */
765
+ await Promise.all(batch.map(async (positionCapInfo) => {
766
+ try {
767
+ const positionRaw = await this.provider.getDynamicFieldObject({
768
+ parentId: this.consts.zoCore.positionsParent,
769
+ name: {
770
+ type: `${this.consts.zoCore.package}::market::PositionName<${positionCapInfo.symbol0}, ${positionCapInfo.symbol1}, ${this.consts.zoCore.package}::market::${positionCapInfo.long ? 'LONG' : 'SHORT'}>`,
771
+ value: {
772
+ owner,
773
+ id: positionCapInfo.positionCapId,
774
+ },
775
+ },
776
+ })
777
+ positionInfoList.push(await this.#parsePositionInfo(positionRaw, positionCapInfo.positionCapId))
778
+ }
779
+ catch (error) {
780
+ // Position might have been deleted after force settlement
781
+ console.warn(`Failed to parse position info for position cap ID ${positionCapInfo.positionCapId}: ${error}`)
782
+ // Continue with next position without adding this one to the list
783
+ }
784
+ }))
785
+ }
786
+
787
+ return positionInfoList.sort((a, b) => a.openTimestamp > b.openTimestamp ? 1 : -1)
788
+ }
789
+
790
+ // find all open positions by positionsParent
791
+ public async getOpenPositions(batchSize = 50, symbol = 'sui') {
792
+ let positionDynamicFields: DynamicFieldInfo[] = []
793
+ let _continue = true
794
+ let cursor
795
+ while (_continue) {
796
+ // data here will be a list of dynamic fields containing name and value
797
+ const { data, nextCursor, hasNextPage }
798
+ /* eslint-disable-next-line no-await-in-loop -- pagination requires sequential fetches */
799
+ = await this.provider.getDynamicFields({
800
+ parentId: this.consts.zoCore.positionsParent,
801
+ cursor,
802
+ })
803
+
804
+ positionDynamicFields = positionDynamicFields.concat(data)
805
+ _continue = hasNextPage
806
+ cursor = nextCursor
807
+ }
808
+
809
+ // Filter by symbol if provided
810
+ if (!(symbol && this.consts.coins[symbol])) {
811
+ return []
812
+ }
813
+ const coinModule = symbol === 'sui' ? '0x2::sui::SUI' : this.consts.coins[symbol].module
814
+ positionDynamicFields = positionDynamicFields.filter((field) => {
815
+ // Extract the second coin module from PositionName<coin1, coin2, direction>
816
+ const typeStr = field.name?.type
817
+ if (!typeStr)
818
+ return false
819
+
820
+ const match = typeStr.match(/PositionName<([^,\s]+),\s*([^,\s]+),\s*([^>\s]+)>/)
821
+ if (!match)
822
+ return false
823
+
824
+ const secondCoin = match[2].trim()
825
+ return secondCoin === coinModule
826
+ })
827
+
828
+ // then we query by dynamic field names and order by time
829
+ const positionInfoList: IPositionInfo[] = []
830
+
831
+ // Process in batches of 50 (or specified batchSize)
832
+ for (let i = 0; i < positionDynamicFields.length; i += batchSize) {
833
+ const batch = positionDynamicFields.slice(i, i + batchSize)
834
+
835
+ /* eslint-disable-next-line no-await-in-loop -- batch processed in parallel via Promise.all */
836
+ await Promise.all(
837
+ batch.map(async (positionDynamicField) => {
838
+ const positionRaw = await this.provider.getDynamicFieldObject({
839
+ parentId: this.consts.zoCore.positionsParent,
840
+ name: positionDynamicField.name,
841
+ })
842
+
843
+ if (positionRaw?.data?.content) {
844
+ // @ts-expect-error -- optional chain on dynamic Move content
845
+ if (positionRaw?.data?.content?.fields?.value?.fields?.closed) {
846
+ // skip closed positions
847
+ return
848
+ }
849
+ const positionInfo = await this.#parsePositionInfo(
850
+ positionRaw,
851
+ positionDynamicField.objectId,
852
+ )
853
+ if (positionInfo) {
854
+ positionInfoList.push(positionInfo)
855
+ }
856
+ }
857
+ }),
858
+ )
859
+ }
860
+
861
+ return positionInfoList
862
+ .filter(positionInfo => !positionInfo.closed)
863
+ .sort((a, b) => (a.openTimestamp > b.openTimestamp ? 1 : -1))
864
+ }
865
+
866
+ public async getOrderCapInfoList(owner: string): Promise<IOrderCapInfo[]> {
867
+ const orderCapInfoList: IOrderCapInfo[] = []
868
+ let cursor: string | undefined | null
869
+ let hasNextPage = true
870
+
871
+ while (hasNextPage) {
872
+ /* eslint-disable-next-line no-await-in-loop -- pagination requires sequential fetches */
873
+ const orderCaps = await this.provider.getOwnedObjects({
874
+ owner,
875
+ filter: {
876
+ MoveModule: {
877
+ package: this.consts.zoCore.package,
878
+ module: 'market',
879
+ },
880
+ },
881
+ options: {
882
+ showType: true,
883
+ showContent: true,
884
+ },
885
+ cursor,
886
+ })
887
+
888
+ for (const orderCap of orderCaps.data) {
889
+ if (orderCap.data?.type?.includes('OrderCap')) {
890
+ orderCapInfoList.push({
891
+ orderCapId: orderCap.data.objectId,
892
+ symbol0: orderCap.data.type.split('<')[1].split(',')[0].trim(),
893
+ symbol1: orderCap.data.type.split('<')[1].split(',')[1].split(',')[0].trim(),
894
+ long: orderCap.data.type.includes('LONG'),
895
+ positionId: (orderCap.data.content as any)?.fields?.position_id,
896
+ })
897
+ }
898
+ }
899
+
900
+ hasNextPage = orderCaps.hasNextPage
901
+ cursor = orderCaps.nextCursor
902
+ }
903
+
904
+ return orderCapInfoList
905
+ }
906
+
907
+ public async getOrderInfoList(orderCapInfoList: IOrderCapInfo[], owner: string, batchSize = 10) {
908
+ const orderInfoList: IOrderInfo[] = []
909
+
910
+ // Process in batches of 10
911
+ for (let i = 0; i < orderCapInfoList.length; i += batchSize) {
912
+ const batch = orderCapInfoList.slice(i, i + batchSize)
913
+
914
+ /* eslint-disable-next-line no-await-in-loop -- batch processed in parallel via Promise.all */
915
+ await Promise.all(batch.map(async (orderCapInfo) => {
916
+ try {
917
+ const orderRaw = await this.provider.getDynamicFieldObject({
918
+ parentId: this.consts.zoCore.ordersParent,
919
+ name: {
920
+ // We have enforced collateral coin type to match with fee coin type so we can use symbol0 in the first slot and the last slot of the OrderName
921
+ type: `${this.consts.zoCore.package}::market::OrderName<${orderCapInfo.symbol0}, ${orderCapInfo.symbol1}, ${this.consts.zoCore.package}::market::${orderCapInfo.long ? 'LONG' : 'SHORT'}, ${orderCapInfo.symbol0}>`,
922
+ value: {
923
+ owner,
924
+ id: orderCapInfo.orderCapId,
925
+ position_id: {
926
+ vec: orderCapInfo.positionId ? [orderCapInfo.positionId] : [],
927
+ },
928
+ },
929
+ },
930
+ })
931
+ orderInfoList.push(this.#parseOrderInfo(orderRaw, orderCapInfo.orderCapId))
932
+ }
933
+ catch (error) {
934
+ // Order might have been deleted
935
+ console.warn(`Failed to parse order info for order cap ID ${orderCapInfo.orderCapId}: ${error}`)
936
+ // Continue with next order without adding this one to the list
937
+ }
938
+ }))
939
+ }
940
+
941
+ return orderInfoList.sort((a, b) => a.createdAt > b.createdAt ? 1 : -1)
942
+ }
943
+
944
+ public async getRebaseFeeModel() {
945
+ this.validateCache()
946
+ if (this.rebaseFeeModelCache) {
947
+ return this.rebaseFeeModelCache
948
+ }
949
+ const rawData = await this.provider.getObject({
950
+ id: this.consts.zoCore.rebaseFeeModel,
951
+ options: {
952
+ showContent: true,
953
+ },
954
+ })
955
+ return this.#parseRebaseFeeModel(rawData)
956
+ }
957
+
958
+ fundingFeeRate = async (indexToken: string, long: boolean) => {
959
+ const symbol = await this.getSymbolInfo(indexToken, long)
960
+ if (symbol.lastUpdate <= 0) {
961
+ return 0
962
+ }
963
+ const price = (await this.getOraclePrice(indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked()
964
+ const lpSupplyAmount = (await this.getMarketInfo()).lpSupplyWithDecimals
965
+ const model = symbol.fundingFeeModel
966
+ const elapsed = SECONDS_PER_EIGHT_HOUR
967
+
968
+ const deltaSize = this.#calcDeltaSize(symbol, price)
969
+ const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount
970
+ return this.#calcFundingFeeRate(model, pnlPerLp, elapsed)
971
+ }
972
+
973
+ rebaseFeeRate = async (collateralToken: string, increase: boolean, amount: number) => {
974
+ let vaultValue = 0
975
+ if (!increase && amount > 0) {
976
+ amount = -amount
977
+ }
978
+ const value = amount * (await this.getOraclePrice(collateralToken)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[collateralToken].decimals)
979
+ const vaultPromises = Object.keys(this.consts.zoCore.vaults).map(async (vault) => {
980
+ const vaultInfo = await this.getVaultInfo(vault)
981
+ const reservingFeeDelta = this.#calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000)
982
+ const res = (reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount) * (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[vault].decimals)
983
+ if (collateralToken === vault) {
984
+ vaultValue = res
985
+ }
986
+ return res
987
+ })
988
+
989
+ const vaultValues = await Promise.all(vaultPromises)
990
+ const totalVaultValue = vaultValues.reduce((acc, curr) => acc + curr, 0)
991
+ const targetRatio = Number.parseInt(
992
+ this.consts.zoCore.vaults[collateralToken].weight,
993
+ 10,
994
+ ) / Object.values(this.consts.zoCore.vaults)
995
+ .map(e => Number.parseInt(e.weight, 10))
996
+ .reduce((acc, curr) => acc + curr, 0)
997
+
998
+ return this.#calcRebaseFeeRate(
999
+ await this.getRebaseFeeModel(),
1000
+ increase,
1001
+ (vaultValue + value) / (totalVaultValue + value),
1002
+ targetRatio,
1003
+ )
1004
+ }
1005
+
1006
+ reservingFeeRate = async (collateralToken: string, amount = 0) => {
1007
+ const vaultInfo = await this.getVaultInfo(collateralToken)
1008
+ const vaultSupply = vaultInfo.liquidity + vaultInfo.reservedAmount + vaultInfo.unrealisedReservingFeeAmount + amount
1009
+ const utilization = vaultSupply ? ((vaultInfo.reservedAmount + amount) / vaultSupply) : 0
1010
+ return this.#calcReservingFeeRate(vaultInfo.reservingFeeModel, utilization, SECONDS_PER_EIGHT_HOUR)
1011
+ }
1012
+
1013
+ getProxiedHistories = async (trader: string, page?: number, limit?: number): Promise<IHistory[]> => {
1014
+ const params = new URLSearchParams({ trader })
1015
+ if (page !== undefined) {
1016
+ params.append('page', page.toString())
1017
+ }
1018
+ if (limit !== undefined) {
1019
+ params.append('limit', limit.toString())
1020
+ }
1021
+
1022
+ const url = `${this.apiEndpoint}/traderEvents?${params}`
1023
+ const res = await fetch(url, {
1024
+ method: 'GET',
1025
+ headers: {
1026
+ 'Content-Type': 'application/json',
1027
+ },
1028
+ })
1029
+ const response = await res.json() as any
1030
+
1031
+ // Handle both old format (array) and new format (object with data)
1032
+ if (Array.isArray(response)) {
1033
+ return response
1034
+ }
1035
+
1036
+ return response.data?.histories || []
1037
+ }
1038
+
1039
+ getProxiedHistoriesWithPagination = async (
1040
+ trader: string,
1041
+ page = 1,
1042
+ limit = 20,
1043
+ orderType?: string,
1044
+ symbol?: string,
1045
+ ) => {
1046
+ const params = new URLSearchParams({
1047
+ trader,
1048
+ page: page.toString(),
1049
+ limit: limit.toString(),
1050
+ })
1051
+
1052
+ // Add filter parameters if provided
1053
+ if (orderType && orderType !== 'all') {
1054
+ params.append('orderType', orderType)
1055
+ }
1056
+ if (symbol && symbol !== 'all') {
1057
+ params.append('symbol', symbol)
1058
+ }
1059
+
1060
+ const url = `${this.apiEndpoint}/traderEvents?${params}`
1061
+ const res = await fetch(url, {
1062
+ method: 'GET',
1063
+ headers: {
1064
+ 'Content-Type': 'application/json',
1065
+ },
1066
+ })
1067
+ const response = await res.json() as any
1068
+
1069
+ return {
1070
+ histories: response.data?.histories || [],
1071
+ pagination: response.data?.pagination || {
1072
+ total: 0,
1073
+ page: 1,
1074
+ limit: 20,
1075
+ pages: 0,
1076
+ },
1077
+ }
1078
+ }
1079
+
1080
+ getStaked = async (owner: string): Promise<IStaked> => {
1081
+ const rawCredentials = await this.provider.getOwnedObjects({
1082
+ owner,
1083
+ filter: {
1084
+ MoveModule: {
1085
+ package: this.consts.zoStaking.package,
1086
+ module: 'pool',
1087
+ },
1088
+ },
1089
+ options: {
1090
+ showType: true,
1091
+ showContent: true,
1092
+ },
1093
+ })
1094
+ const pool = await this.getStakePool()
1095
+ const credentials = rawCredentials.data.map((item: any) => this.#parseCredential(item, pool))
1096
+ return {
1097
+ credentials,
1098
+ amount: credentials.reduce((acc: bigint, cur: ICredential) => acc + cur.amount, BigInt(0)),
1099
+ claimable: credentials.reduce((acc: bigint, cur: ICredential) => acc + cur.claimable, BigInt(0)),
1100
+ }
1101
+ }
1102
+
1103
+ getStakePool = async (): Promise<IStakePool> => {
1104
+ const raw = await this.provider.getObject({
1105
+ id: this.consts.zoStaking.pool,
1106
+ options: {
1107
+ showContent: true,
1108
+ },
1109
+ })
1110
+ return this.#parseStakePool(raw)
1111
+ }
1112
+
1113
+ #parseStakePool(raw: any): IStakePool {
1114
+ const content = raw.data.content.fields
1115
+ const pool = {
1116
+ id: content.id.id,
1117
+ enabled: content.enabled,
1118
+ lastUpdatedTime: parseValue(content.last_updated_time),
1119
+ stakedAmount: BigInt(content.staked_amount),
1120
+ reward: BigInt(content.reward),
1121
+ startTime: parseValue(content.start_time),
1122
+ endTime: parseValue(content.end_time),
1123
+ accRewardPerShare: BigInt(content.acc_reward_per_share),
1124
+ lockDuration: parseValue(content.lock_duration),
1125
+ }
1126
+ this.#refreshPool(pool, Math.floor(Date.now() / 1000))
1127
+ return pool
1128
+ }
1129
+
1130
+ #parseCredential(raw: any, pool: IStakePool): ICredential {
1131
+ const stakedAmount = BigInt(raw.data.content.fields.stake)
1132
+ const accRewardPerShare = BigInt(raw.data.content.fields.acc_reward_per_share)
1133
+ return {
1134
+ id: raw.data.objectId,
1135
+ lockUntil: parseValue(raw.data.content.fields.lock_until),
1136
+ amount: stakedAmount,
1137
+ accRewardPerShare,
1138
+ claimable: ((pool.accRewardPerShare - accRewardPerShare) * stakedAmount) / BigInt(1e18),
1139
+ }
1140
+ }
1141
+
1142
+ #refreshPool(pool: IStakePool, timestamp: number): void {
1143
+ if (timestamp === pool.lastUpdatedTime || timestamp < pool.startTime) {
1144
+ return
1145
+ }
1146
+ if (pool.lastUpdatedTime === pool.endTime || pool.stakedAmount === BigInt(0)) {
1147
+ return
1148
+ }
1149
+ if (timestamp > pool.endTime) {
1150
+ timestamp = pool.endTime
1151
+ }
1152
+
1153
+ const rewardAmount = pool.reward * BigInt(timestamp - pool.lastUpdatedTime)
1154
+ / BigInt(pool.endTime - pool.lastUpdatedTime)
1155
+
1156
+ pool.lastUpdatedTime = timestamp
1157
+ const rewardPerShare = rewardAmount * BigInt(1e18) / pool.stakedAmount
1158
+ pool.accRewardPerShare += rewardPerShare
1159
+ }
1160
+
1161
+ async getReferralData(referree: string): Promise<any> {
1162
+ const raw = await this.provider.getDynamicFieldObject({
1163
+ parentId: this.consts.zoCore.referralsParent,
1164
+ name: {
1165
+ type: 'address',
1166
+ value: referree,
1167
+ },
1168
+ })
1169
+ return raw
1170
+ }
1171
+
1172
+ async hasReferral(referree: string): Promise<boolean> {
1173
+ const raw = await this.getReferralData(referree)
1174
+ return !raw.error
1175
+ }
1176
+
1177
+ async getReferreeFromReferrer(referrer: string): Promise<any> {
1178
+ const url = new URL(`${this.apiEndpoint}/executions`)
1179
+ const data = {
1180
+ projectId: `${this.network === 'mainnet' ? 'nCBQRfhN' : 'C5iFbF6X'}`,
1181
+ projectSlug: `zo-${this.network}`,
1182
+ projectOwner: 'zo',
1183
+ sqlQuery: {
1184
+ sql: `SELECT referralSender, timestamp from \`Referral_Added\` WHERE referralReferrer = '${referrer}'`,
1185
+ size: 100,
1186
+ },
1187
+ }
1188
+ const res = await fetch(url, {
1189
+ method: 'POST',
1190
+ headers: {
1191
+ 'Content-Type': 'application/json',
1192
+ },
1193
+ body: JSON.stringify(data),
1194
+ })
1195
+ return (await res.json()).result.rows.map((e: any) => ({ address: e.referralSender, date: new Date(e.timestamp) }))
1196
+ }
1197
+
1198
+ async getReferreeDetail(referree: string): Promise<any> {
1199
+ const url = new URL(`${this.apiEndpoint}/executions`)
1200
+ const data = {
1201
+ projectId: `${this.network === 'mainnet' ? 'nCBQRfhN' : 'C5iFbF6X'}`,
1202
+ projectSlug: `zo-${this.network}`,
1203
+ projectOwner: 'zo',
1204
+ sqlQuery: {
1205
+ sql: `SELECT sum(volume) as volumeUsd, sum(rebate) as rebateUsd FROM \`User_Interaction\` WHERE distinct_id = '${referree}' and rebate != 0`,
1206
+ size: 100,
1207
+ },
1208
+ }
1209
+ const res = await fetch(url, {
1210
+ method: 'POST',
1211
+ headers: {
1212
+ 'Content-Type': 'application/json',
1213
+ },
1214
+ body: JSON.stringify(data),
1215
+ })
1216
+ const row = (await res.json()).result.rows[0]
1217
+ return {
1218
+ volumeUsd: Number.parseFloat(row.volumeUsd),
1219
+ rebateUsd: Number.parseFloat(row.rebateUsd),
1220
+ }
1221
+ }
1222
+
1223
+ async getReferrerDetail(referrer: string): Promise<any> {
1224
+ const url = new URL(`${this.apiEndpoint}/executions`)
1225
+ const data = {
1226
+ projectId: `${this.network === 'mainnet' ? 'nCBQRfhN' : 'C5iFbF6X'}`,
1227
+ projectSlug: `zo-${this.network}`,
1228
+ projectOwner: 'zo',
1229
+ sqlQuery: {
1230
+ sql: `SELECT sum(volume) as volumeUsd, sum(rebate) as rebateUsd FROM \`User_Interaction\` WHERE referralReceiver = '${referrer}' and rebate != 0`,
1231
+ size: 100,
1232
+ },
1233
+ }
1234
+ const res = await fetch(url, {
1235
+ method: 'POST',
1236
+ headers: {
1237
+ 'Content-Type': 'application/json',
1238
+ },
1239
+ body: JSON.stringify(data),
1240
+ })
1241
+ const row = (await res.json()).result.rows[0]
1242
+ return {
1243
+ volumeUsd: Number.parseFloat(row.volumeUsd),
1244
+ rebateUsd: Number.parseFloat(row.rebateUsd),
1245
+ }
1246
+ }
1247
+
1248
+ async getReferralTxs(referrer: string): Promise<any> {
1249
+ const url = new URL(`${this.apiEndpoint}/executions`)
1250
+ const data = {
1251
+ projectId: `${this.network === 'mainnet' ? 'nCBQRfhN' : 'C5iFbF6X'}`,
1252
+ projectSlug: `zo-${this.network}`,
1253
+ projectOwner: 'zo',
1254
+ sqlQuery: {
1255
+ sql: `SELECT distinct_id, volume, rebate, timestamp, transaction_hash FROM \`User_Interaction\` WHERE referralReceiver = '${referrer}' and rebate != 0 order by timestamp desc`,
1256
+ size: 100,
1257
+ },
1258
+ }
1259
+ const res = await fetch(url, {
1260
+ method: 'POST',
1261
+ headers: {
1262
+ 'Content-Type': 'application/json',
1263
+ },
1264
+ body: JSON.stringify(data),
1265
+ })
1266
+
1267
+ const { rows } = (await res.json()).result
1268
+
1269
+ return rows.map((e: any) => {
1270
+ return {
1271
+ referree: e.distinct_id,
1272
+ volume: Number.parseFloat(e.volume),
1273
+ rebate: Number.parseFloat(e.rebate),
1274
+ date: new Date(e.timestamp),
1275
+ tx: e.transaction_hash,
1276
+ }
1277
+ })
1278
+ }
1279
+ }