@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.
- package/.claude/settings.local.json +9 -0
- package/.gitattributes +4 -0
- package/.prettierrc.js +9 -0
- package/README.md +28 -0
- package/dist/abstract/BaseAPI.cjs +206 -0
- package/dist/abstract/BaseAPI.cjs.map +1 -0
- package/dist/abstract/BaseAPI.d.cts +172 -0
- package/dist/abstract/BaseAPI.d.cts.map +1 -0
- package/dist/abstract/BaseAPI.d.mts +172 -0
- package/dist/abstract/BaseAPI.d.mts.map +1 -0
- package/dist/abstract/BaseAPI.mjs +202 -0
- package/dist/abstract/BaseAPI.mjs.map +1 -0
- package/dist/abstract/BaseDataAPI.cjs +140 -0
- package/dist/abstract/BaseDataAPI.cjs.map +1 -0
- package/dist/abstract/BaseDataAPI.d.cts +89 -0
- package/dist/abstract/BaseDataAPI.d.cts.map +1 -0
- package/dist/abstract/BaseDataAPI.d.mts +89 -0
- package/dist/abstract/BaseDataAPI.d.mts.map +1 -0
- package/dist/abstract/BaseDataAPI.mjs +136 -0
- package/dist/abstract/BaseDataAPI.mjs.map +1 -0
- package/dist/abstract/index.cjs +12 -0
- package/dist/abstract/index.cjs.map +1 -0
- package/dist/abstract/index.d.cts +7 -0
- package/dist/abstract/index.d.cts.map +1 -0
- package/dist/abstract/index.d.mts +7 -0
- package/dist/abstract/index.d.mts.map +1 -0
- package/dist/abstract/index.mjs +7 -0
- package/dist/abstract/index.mjs.map +1 -0
- package/dist/api.cjs +779 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.cts +75 -0
- package/dist/api.d.cts.map +1 -0
- package/dist/api.d.mts +75 -0
- package/dist/api.d.mts.map +1 -0
- package/dist/api.mjs +775 -0
- package/dist/api.mjs.map +1 -0
- package/dist/bcs.cjs +42 -0
- package/dist/bcs.cjs.map +1 -0
- package/dist/bcs.d.cts +91 -0
- package/dist/bcs.d.cts.map +1 -0
- package/dist/bcs.d.mts +91 -0
- package/dist/bcs.d.mts.map +1 -0
- package/dist/bcs.mjs +39 -0
- package/dist/bcs.mjs.map +1 -0
- package/dist/consts/deployments-shared-mainnet.json +50 -0
- package/dist/consts/deployments-shared-testnet.json +45 -0
- package/dist/consts/deployments-slp-mainnet.json +600 -0
- package/dist/consts/deployments-slp-testnet.json +87 -0
- package/dist/consts/deployments-usdz-mainnet.json +494 -0
- package/dist/consts/deployments-usdz-testnet.json +98 -0
- package/dist/consts/deployments-zbtcvc-mainnet.json +180 -0
- package/dist/consts/deployments-zlp-mainnet.json +791 -0
- package/dist/consts/deployments-zlp-testnet.json +76 -0
- package/dist/consts/index.cjs +200 -0
- package/dist/consts/index.cjs.map +1 -0
- package/dist/consts/index.d.cts +157 -0
- package/dist/consts/index.d.cts.map +1 -0
- package/dist/consts/index.d.mts +157 -0
- package/dist/consts/index.d.mts.map +1 -0
- package/dist/consts/index.mjs +189 -0
- package/dist/consts/index.mjs.map +1 -0
- package/dist/consts/price_id_to_object_id.mainnet.json +56 -0
- package/dist/consts/price_id_to_object_id.testnet.json +17 -0
- package/dist/data.cjs +919 -0
- package/dist/data.cjs.map +1 -0
- package/dist/data.d.cts +235 -0
- package/dist/data.d.cts.map +1 -0
- package/dist/data.d.mts +235 -0
- package/dist/data.d.mts.map +1 -0
- package/dist/data.mjs +915 -0
- package/dist/data.mjs.map +1 -0
- package/dist/factory/SDKFactory.cjs +228 -0
- package/dist/factory/SDKFactory.cjs.map +1 -0
- package/dist/factory/SDKFactory.d.cts +84 -0
- package/dist/factory/SDKFactory.d.cts.map +1 -0
- package/dist/factory/SDKFactory.d.mts +84 -0
- package/dist/factory/SDKFactory.d.mts.map +1 -0
- package/dist/factory/SDKFactory.mjs +222 -0
- package/dist/factory/SDKFactory.mjs.map +1 -0
- package/dist/implementations/SLPAPI.cjs +1794 -0
- package/dist/implementations/SLPAPI.cjs.map +1 -0
- package/dist/implementations/SLPAPI.d.cts +183 -0
- package/dist/implementations/SLPAPI.d.cts.map +1 -0
- package/dist/implementations/SLPAPI.d.mts +183 -0
- package/dist/implementations/SLPAPI.d.mts.map +1 -0
- package/dist/implementations/SLPAPI.mjs +1790 -0
- package/dist/implementations/SLPAPI.mjs.map +1 -0
- package/dist/implementations/SLPDataAPI.cjs +1384 -0
- package/dist/implementations/SLPDataAPI.cjs.map +1 -0
- package/dist/implementations/SLPDataAPI.d.cts +158 -0
- package/dist/implementations/SLPDataAPI.d.cts.map +1 -0
- package/dist/implementations/SLPDataAPI.d.mts +158 -0
- package/dist/implementations/SLPDataAPI.d.mts.map +1 -0
- package/dist/implementations/SLPDataAPI.mjs +1380 -0
- package/dist/implementations/SLPDataAPI.mjs.map +1 -0
- package/dist/implementations/USDZAPI.cjs +1676 -0
- package/dist/implementations/USDZAPI.cjs.map +1 -0
- package/dist/implementations/USDZAPI.d.cts +180 -0
- package/dist/implementations/USDZAPI.d.cts.map +1 -0
- package/dist/implementations/USDZAPI.d.mts +180 -0
- package/dist/implementations/USDZAPI.d.mts.map +1 -0
- package/dist/implementations/USDZAPI.mjs +1672 -0
- package/dist/implementations/USDZAPI.mjs.map +1 -0
- package/dist/implementations/USDZDataAPI.cjs +1209 -0
- package/dist/implementations/USDZDataAPI.cjs.map +1 -0
- package/dist/implementations/USDZDataAPI.d.cts +191 -0
- package/dist/implementations/USDZDataAPI.d.cts.map +1 -0
- package/dist/implementations/USDZDataAPI.d.mts +191 -0
- package/dist/implementations/USDZDataAPI.d.mts.map +1 -0
- package/dist/implementations/USDZDataAPI.mjs +1205 -0
- package/dist/implementations/USDZDataAPI.mjs.map +1 -0
- package/dist/implementations/ZBTCVCAPI.cjs +906 -0
- package/dist/implementations/ZBTCVCAPI.cjs.map +1 -0
- package/dist/implementations/ZBTCVCAPI.d.cts +107 -0
- package/dist/implementations/ZBTCVCAPI.d.cts.map +1 -0
- package/dist/implementations/ZBTCVCAPI.d.mts +107 -0
- package/dist/implementations/ZBTCVCAPI.d.mts.map +1 -0
- package/dist/implementations/ZBTCVCAPI.mjs +902 -0
- package/dist/implementations/ZBTCVCAPI.mjs.map +1 -0
- package/dist/implementations/ZBTCVCDataAPI.cjs +829 -0
- package/dist/implementations/ZBTCVCDataAPI.cjs.map +1 -0
- package/dist/implementations/ZBTCVCDataAPI.d.cts +94 -0
- package/dist/implementations/ZBTCVCDataAPI.d.cts.map +1 -0
- package/dist/implementations/ZBTCVCDataAPI.d.mts +94 -0
- package/dist/implementations/ZBTCVCDataAPI.d.mts.map +1 -0
- package/dist/implementations/ZBTCVCDataAPI.mjs +825 -0
- package/dist/implementations/ZBTCVCDataAPI.mjs.map +1 -0
- package/dist/implementations/ZLPAPI.cjs +1948 -0
- package/dist/implementations/ZLPAPI.cjs.map +1 -0
- package/dist/implementations/ZLPAPI.d.cts +192 -0
- package/dist/implementations/ZLPAPI.d.cts.map +1 -0
- package/dist/implementations/ZLPAPI.d.mts +192 -0
- package/dist/implementations/ZLPAPI.d.mts.map +1 -0
- package/dist/implementations/ZLPAPI.mjs +1944 -0
- package/dist/implementations/ZLPAPI.mjs.map +1 -0
- package/dist/implementations/ZLPDataAPI.cjs +1267 -0
- package/dist/implementations/ZLPDataAPI.cjs.map +1 -0
- package/dist/implementations/ZLPDataAPI.d.cts +193 -0
- package/dist/implementations/ZLPDataAPI.d.cts.map +1 -0
- package/dist/implementations/ZLPDataAPI.d.mts +193 -0
- package/dist/implementations/ZLPDataAPI.d.mts.map +1 -0
- package/dist/implementations/ZLPDataAPI.mjs +1263 -0
- package/dist/implementations/ZLPDataAPI.mjs.map +1 -0
- package/dist/implementations/index.cjs +26 -0
- package/dist/implementations/index.cjs.map +1 -0
- package/dist/implementations/index.d.cts +13 -0
- package/dist/implementations/index.d.cts.map +1 -0
- package/dist/implementations/index.d.mts +13 -0
- package/dist/implementations/index.d.mts.map +1 -0
- package/dist/implementations/index.mjs +15 -0
- package/dist/implementations/index.mjs.map +1 -0
- package/dist/index.cjs +69 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +51 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +51 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +51 -0
- package/dist/index.mjs.map +1 -0
- package/dist/interfaces/base.cjs +7 -0
- package/dist/interfaces/base.cjs.map +1 -0
- package/dist/interfaces/base.d.cts +346 -0
- package/dist/interfaces/base.d.cts.map +1 -0
- package/dist/interfaces/base.d.mts +346 -0
- package/dist/interfaces/base.d.mts.map +1 -0
- package/dist/interfaces/base.mjs +6 -0
- package/dist/interfaces/base.mjs.map +1 -0
- package/dist/interfaces/index.cjs +31 -0
- package/dist/interfaces/index.cjs.map +1 -0
- package/dist/interfaces/index.d.cts +15 -0
- package/dist/interfaces/index.d.cts.map +1 -0
- package/dist/interfaces/index.d.mts +15 -0
- package/dist/interfaces/index.d.mts.map +1 -0
- package/dist/interfaces/index.mjs +15 -0
- package/dist/interfaces/index.mjs.map +1 -0
- package/dist/interfaces/slp.cjs +7 -0
- package/dist/interfaces/slp.cjs.map +1 -0
- package/dist/interfaces/slp.d.cts +179 -0
- package/dist/interfaces/slp.d.cts.map +1 -0
- package/dist/interfaces/slp.d.mts +179 -0
- package/dist/interfaces/slp.d.mts.map +1 -0
- package/dist/interfaces/slp.mjs +6 -0
- package/dist/interfaces/slp.mjs.map +1 -0
- package/dist/interfaces/usdz.cjs +7 -0
- package/dist/interfaces/usdz.cjs.map +1 -0
- package/dist/interfaces/usdz.d.cts +104 -0
- package/dist/interfaces/usdz.d.cts.map +1 -0
- package/dist/interfaces/usdz.d.mts +104 -0
- package/dist/interfaces/usdz.d.mts.map +1 -0
- package/dist/interfaces/usdz.mjs +6 -0
- package/dist/interfaces/usdz.mjs.map +1 -0
- package/dist/interfaces/zbtcvc.cjs +7 -0
- package/dist/interfaces/zbtcvc.cjs.map +1 -0
- package/dist/interfaces/zbtcvc.d.cts +64 -0
- package/dist/interfaces/zbtcvc.d.cts.map +1 -0
- package/dist/interfaces/zbtcvc.d.mts +64 -0
- package/dist/interfaces/zbtcvc.d.mts.map +1 -0
- package/dist/interfaces/zbtcvc.mjs +6 -0
- package/dist/interfaces/zbtcvc.mjs.map +1 -0
- package/dist/interfaces/zlp.cjs +7 -0
- package/dist/interfaces/zlp.cjs.map +1 -0
- package/dist/interfaces/zlp.d.cts +114 -0
- package/dist/interfaces/zlp.d.cts.map +1 -0
- package/dist/interfaces/zlp.d.mts +114 -0
- package/dist/interfaces/zlp.d.mts.map +1 -0
- package/dist/interfaces/zlp.mjs +6 -0
- package/dist/interfaces/zlp.mjs.map +1 -0
- package/dist/oracle.cjs +118 -0
- package/dist/oracle.cjs.map +1 -0
- package/dist/oracle.d.cts +25 -0
- package/dist/oracle.d.cts.map +1 -0
- package/dist/oracle.d.mts +25 -0
- package/dist/oracle.d.mts.map +1 -0
- package/dist/oracle.mjs +114 -0
- package/dist/oracle.mjs.map +1 -0
- package/dist/utils.cjs +129 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +44 -0
- package/dist/utils.d.cts.map +1 -0
- package/dist/utils.d.mts +44 -0
- package/dist/utils.d.mts.map +1 -0
- package/dist/utils.mjs +115 -0
- package/dist/utils.mjs.map +1 -0
- package/docs/SUMMARY.md +10 -0
- package/docs/api-reference.md +32 -0
- package/docs/architecture.md +14 -0
- package/docs/common-operations.md +52 -0
- package/docs/error-handling.md +17 -0
- package/docs/getting-started.md +60 -0
- package/docs/introduction.md +15 -0
- package/docs/lp-specific-features.md +96 -0
- package/docs/type-safety.md +29 -0
- package/eslint.config.mjs +18 -0
- package/package.json +42 -0
- package/src/abstract/BaseAPI.ts +575 -0
- package/src/abstract/BaseDataAPI.ts +207 -0
- package/src/abstract/index.ts +7 -0
- package/src/api.ts +1100 -0
- package/src/bcs.ts +45 -0
- package/src/consts/deployments-shared-mainnet.json +50 -0
- package/src/consts/deployments-shared-testnet.json +45 -0
- package/src/consts/deployments-slp-mainnet.json +600 -0
- package/src/consts/deployments-slp-testnet.json +87 -0
- package/src/consts/deployments-usdz-mainnet.json +494 -0
- package/src/consts/deployments-usdz-testnet.json +98 -0
- package/src/consts/deployments-zbtcvc-mainnet.json +180 -0
- package/src/consts/deployments-zlp-mainnet.json +791 -0
- package/src/consts/deployments-zlp-testnet.json +76 -0
- package/src/consts/index.ts +345 -0
- package/src/consts/price_id_to_object_id.mainnet.json +56 -0
- package/src/consts/price_id_to_object_id.testnet.json +17 -0
- package/src/data.ts +1279 -0
- package/src/factory/SDKFactory.ts +340 -0
- package/src/implementations/SLPAPI.ts +2722 -0
- package/src/implementations/SLPDataAPI.ts +1839 -0
- package/src/implementations/USDZAPI.ts +2488 -0
- package/src/implementations/USDZDataAPI.ts +1548 -0
- package/src/implementations/ZBTCVCAPI.ts +1337 -0
- package/src/implementations/ZBTCVCDataAPI.ts +993 -0
- package/src/implementations/ZLPAPI.ts +2888 -0
- package/src/implementations/ZLPDataAPI.ts +1603 -0
- package/src/implementations/index.ts +16 -0
- package/src/index.ts +58 -0
- package/src/interfaces/base.ts +838 -0
- package/src/interfaces/index.ts +50 -0
- package/src/interfaces/slp.ts +268 -0
- package/src/interfaces/usdz.ts +181 -0
- package/src/interfaces/zbtcvc.ts +116 -0
- package/src/interfaces/zlp.ts +244 -0
- package/src/oracle.ts +153 -0
- package/src/utils.ts +168 -0
- package/tests/api.test.ts +219 -0
- package/tests/data.test.ts +156 -0
- package/tests/oracle.test.ts +33 -0
- 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
|
+
}
|