zo-sdk 0.1.21 → 0.1.23
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/dist/abstract/BaseDataAPI.d.cts +2 -2
- package/dist/abstract/BaseDataAPI.d.cts.map +1 -1
- package/dist/abstract/BaseDataAPI.d.mts +2 -2
- package/dist/abstract/BaseDataAPI.d.mts.map +1 -1
- package/dist/consts/deployments-zbtcvc-mainnet.json +180 -0
- package/dist/consts/index.cjs +27 -20
- package/dist/consts/index.cjs.map +1 -1
- package/dist/consts/index.d.cts +5 -17
- package/dist/consts/index.d.cts.map +1 -1
- package/dist/consts/index.d.mts +5 -17
- package/dist/consts/index.d.mts.map +1 -1
- package/dist/consts/index.mjs +26 -19
- package/dist/consts/index.mjs.map +1 -1
- package/dist/factory/SDKFactory.cjs +36 -0
- package/dist/factory/SDKFactory.cjs.map +1 -1
- package/dist/factory/SDKFactory.d.cts +11 -1
- package/dist/factory/SDKFactory.d.cts.map +1 -1
- package/dist/factory/SDKFactory.d.mts +11 -1
- package/dist/factory/SDKFactory.d.mts.map +1 -1
- package/dist/factory/SDKFactory.mjs +36 -0
- package/dist/factory/SDKFactory.mjs.map +1 -1
- package/dist/implementations/SLPDataAPI.cjs +54 -32
- package/dist/implementations/SLPDataAPI.cjs.map +1 -1
- package/dist/implementations/SLPDataAPI.d.cts +2 -2
- package/dist/implementations/SLPDataAPI.d.cts.map +1 -1
- package/dist/implementations/SLPDataAPI.d.mts +2 -2
- package/dist/implementations/SLPDataAPI.d.mts.map +1 -1
- package/dist/implementations/SLPDataAPI.mjs +54 -32
- package/dist/implementations/SLPDataAPI.mjs.map +1 -1
- package/dist/implementations/USDZAPI.cjs +1 -1
- package/dist/implementations/USDZAPI.cjs.map +1 -1
- package/dist/implementations/USDZAPI.d.cts +1 -1
- package/dist/implementations/USDZAPI.d.cts.map +1 -1
- package/dist/implementations/USDZAPI.d.mts +1 -1
- package/dist/implementations/USDZAPI.d.mts.map +1 -1
- package/dist/implementations/USDZAPI.mjs +1 -1
- package/dist/implementations/USDZAPI.mjs.map +1 -1
- package/dist/implementations/ZBTCVCAPI.cjs +984 -0
- package/dist/implementations/ZBTCVCAPI.cjs.map +1 -0
- package/dist/implementations/ZBTCVCAPI.d.cts +133 -0
- package/dist/implementations/ZBTCVCAPI.d.cts.map +1 -0
- package/dist/implementations/ZBTCVCAPI.d.mts +133 -0
- package/dist/implementations/ZBTCVCAPI.d.mts.map +1 -0
- package/dist/implementations/ZBTCVCAPI.mjs +980 -0
- package/dist/implementations/ZBTCVCAPI.mjs.map +1 -0
- package/dist/implementations/ZBTCVCDataAPI.cjs +824 -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 +820 -0
- package/dist/implementations/ZBTCVCDataAPI.mjs.map +1 -0
- package/dist/implementations/index.cjs +5 -1
- package/dist/implementations/index.cjs.map +1 -1
- package/dist/implementations/index.d.cts +2 -0
- package/dist/implementations/index.d.cts.map +1 -1
- package/dist/implementations/index.d.mts +2 -0
- package/dist/implementations/index.d.mts.map +1 -1
- package/dist/implementations/index.mjs +2 -0
- package/dist/implementations/index.mjs.map +1 -1
- package/dist/interfaces/base.d.cts +2 -2
- package/dist/interfaces/base.d.cts.map +1 -1
- package/dist/interfaces/base.d.mts +2 -2
- package/dist/interfaces/base.d.mts.map +1 -1
- package/dist/interfaces/index.cjs +2 -0
- package/dist/interfaces/index.cjs.map +1 -1
- package/dist/interfaces/index.d.cts +2 -0
- package/dist/interfaces/index.d.cts.map +1 -1
- package/dist/interfaces/index.d.mts +2 -0
- package/dist/interfaces/index.d.mts.map +1 -1
- package/dist/interfaces/index.mjs +2 -0
- package/dist/interfaces/index.mjs.map +1 -1
- 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/package.json +8 -8
- package/src/abstract/BaseDataAPI.ts +2 -2
- package/src/consts/deployments-zbtcvc-mainnet.json +180 -0
- package/src/consts/index.ts +28 -35
- package/src/factory/SDKFactory.ts +50 -0
- package/src/implementations/SLPDataAPI.ts +71 -41
- package/src/implementations/USDZAPI.ts +1 -1
- package/src/implementations/ZBTCVCAPI.ts +1453 -0
- package/src/implementations/ZBTCVCDataAPI.ts +985 -0
- package/src/implementations/index.ts +2 -0
- package/src/interfaces/base.ts +2 -2
- package/src/interfaces/index.ts +8 -0
- package/src/interfaces/zbtcvc.ts +115 -0
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
/**
|
|
3
|
+
* ZBTCVC DataAPI implementation
|
|
4
|
+
* Implements ZBTCVC-specific data access methods
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { DynamicFieldInfo, SuiClient } from '@mysten/sui/client'
|
|
8
|
+
import type { Transaction } from '@mysten/sui/transactions'
|
|
9
|
+
import { SUI_CLOCK_OBJECT_ID } from '@mysten/sui/utils'
|
|
10
|
+
|
|
11
|
+
import { BaseDataAPI } from '../abstract'
|
|
12
|
+
import type { Network } from '../consts'
|
|
13
|
+
import { LPToken, SECONDS_PER_EIGHT_HOUR, ZBTCVC_TOKEN_DECIMALS } from '../consts'
|
|
14
|
+
import type {
|
|
15
|
+
IBaseHistoryResponse,
|
|
16
|
+
IBaseStaked,
|
|
17
|
+
IZBTCVCCredential,
|
|
18
|
+
IZBTCVCDataAPI,
|
|
19
|
+
IZBTCVCFundingFeeModel,
|
|
20
|
+
IZBTCVCMarketInfo,
|
|
21
|
+
IZBTCVCMarketValuationInfo,
|
|
22
|
+
IZBTCVCOrderCapInfo,
|
|
23
|
+
IZBTCVCOrderInfo,
|
|
24
|
+
IZBTCVCPositionCapInfo,
|
|
25
|
+
IZBTCVCPositionConfig,
|
|
26
|
+
IZBTCVCPositionInfo,
|
|
27
|
+
IZBTCVCRebaseFeeModel,
|
|
28
|
+
IZBTCVCReservingFeeModel,
|
|
29
|
+
IZBTCVCStakePool,
|
|
30
|
+
IZBTCVCSymbolConfig,
|
|
31
|
+
IZBTCVCSymbolInfo,
|
|
32
|
+
IZBTCVCVaultInfo,
|
|
33
|
+
} from '../interfaces'
|
|
34
|
+
import { joinSymbol, parseSymbolKey, parseValue, suiSymbolToSymbol } from '../utils'
|
|
35
|
+
|
|
36
|
+
export class ZBTCVCDataAPI extends BaseDataAPI implements IZBTCVCDataAPI {
|
|
37
|
+
constructor(
|
|
38
|
+
network: Network,
|
|
39
|
+
provider: SuiClient,
|
|
40
|
+
apiEndpoint: string,
|
|
41
|
+
connectionURL: string,
|
|
42
|
+
) {
|
|
43
|
+
super(network, provider, apiEndpoint, connectionURL, LPToken.ZBTCVC)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async getStaked(owner: string): Promise<IBaseStaked> {
|
|
47
|
+
let rawCredentialsData: any[] = []
|
|
48
|
+
let queryNextPage = true
|
|
49
|
+
let queryCursor: string | undefined | null
|
|
50
|
+
|
|
51
|
+
const limit = 50
|
|
52
|
+
|
|
53
|
+
while (queryNextPage) {
|
|
54
|
+
const { data, hasNextPage, nextCursor } = await this.provider.getOwnedObjects({
|
|
55
|
+
owner,
|
|
56
|
+
filter: {
|
|
57
|
+
MoveModule: {
|
|
58
|
+
package: this.consts.zoStaking.package,
|
|
59
|
+
module: 'pool',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
options: {
|
|
63
|
+
showType: true,
|
|
64
|
+
showContent: true,
|
|
65
|
+
},
|
|
66
|
+
cursor: queryCursor,
|
|
67
|
+
limit,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
queryNextPage = hasNextPage
|
|
71
|
+
queryCursor = nextCursor!
|
|
72
|
+
if (!data)
|
|
73
|
+
break
|
|
74
|
+
rawCredentialsData = [...rawCredentialsData, ...data]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const pool = await this.getStakePool()
|
|
78
|
+
|
|
79
|
+
const credentials = rawCredentialsData.map((item: any) =>
|
|
80
|
+
ZBTCVCDataAPI.parseCredential(item, pool),
|
|
81
|
+
)
|
|
82
|
+
return {
|
|
83
|
+
credentials,
|
|
84
|
+
amount: credentials.reduce(
|
|
85
|
+
(acc: bigint, cur: IZBTCVCCredential) => acc + cur.amount,
|
|
86
|
+
BigInt(0),
|
|
87
|
+
),
|
|
88
|
+
claimable: credentials.reduce(
|
|
89
|
+
(acc: bigint, cur: IZBTCVCCredential) => acc + cur.claimable,
|
|
90
|
+
BigInt(0),
|
|
91
|
+
),
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public async getStakePool(): Promise<IZBTCVCStakePool> {
|
|
96
|
+
const raw = await this.provider.getObject({
|
|
97
|
+
id: this.consts.zoStaking.pools[0],
|
|
98
|
+
options: {
|
|
99
|
+
showContent: true,
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
return ZBTCVCDataAPI.parseStakePool(raw)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates vaults valuation for ZBTCVC
|
|
107
|
+
*/
|
|
108
|
+
public valuateVaults(tx: Transaction) {
|
|
109
|
+
const vaultsValuation = tx.moveCall({
|
|
110
|
+
target: `${this.consts.zoCore.upgradedPackage}::market::create_vaults_valuation`,
|
|
111
|
+
typeArguments: [`${this.consts.zoCore.package}::zbtcvc::ZBTCVC`],
|
|
112
|
+
arguments: [
|
|
113
|
+
tx.object(SUI_CLOCK_OBJECT_ID),
|
|
114
|
+
tx.object(this.consts.zoCore.market),
|
|
115
|
+
],
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
for (const key of Object.keys(this.consts.zoCore.vaults)) {
|
|
119
|
+
const vault = this.consts.zoCore.vaults[key]
|
|
120
|
+
tx.moveCall({
|
|
121
|
+
target: `${this.consts.zoCore.upgradedPackage}::market::valuate_vault`,
|
|
122
|
+
typeArguments: [
|
|
123
|
+
`${this.consts.zoCore.package}::zbtcvc::ZBTCVC`,
|
|
124
|
+
this.consts.coins[key].module,
|
|
125
|
+
],
|
|
126
|
+
arguments: [
|
|
127
|
+
tx.object(this.consts.zoCore.market),
|
|
128
|
+
tx.object(vault.reservingFeeModel),
|
|
129
|
+
tx.object(this.consts.pythFeeder.feeder[key]),
|
|
130
|
+
vaultsValuation,
|
|
131
|
+
],
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
return vaultsValuation
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Creates symbols valuation for ZBTCVC
|
|
139
|
+
*/
|
|
140
|
+
public valuateSymbols(tx: Transaction) {
|
|
141
|
+
const symbolsValuation = tx.moveCall({
|
|
142
|
+
target: `${this.consts.zoCore.upgradedPackage}::market::create_symbols_valuation`,
|
|
143
|
+
typeArguments: [`${this.consts.zoCore.package}::zbtcvc::ZBTCVC`],
|
|
144
|
+
arguments: [
|
|
145
|
+
tx.object(SUI_CLOCK_OBJECT_ID),
|
|
146
|
+
tx.object(this.consts.zoCore.market),
|
|
147
|
+
],
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
for (const key of Object.keys(this.consts.zoCore.symbols)) {
|
|
151
|
+
const [direction, token] = parseSymbolKey(key)
|
|
152
|
+
const symbol = this.consts.zoCore.symbols[key]
|
|
153
|
+
tx.moveCall({
|
|
154
|
+
target: `${this.consts.zoCore.upgradedPackage}::market::valuate_symbol`,
|
|
155
|
+
typeArguments: [
|
|
156
|
+
`${this.consts.zoCore.package}::zbtcvc::ZBTCVC`,
|
|
157
|
+
this.consts.coins[token].module,
|
|
158
|
+
`${this.consts.zoCore.package}::market::${direction.toUpperCase()}`,
|
|
159
|
+
],
|
|
160
|
+
arguments: [
|
|
161
|
+
tx.object(this.consts.zoCore.market),
|
|
162
|
+
tx.object(symbol.fundingFeeModel),
|
|
163
|
+
tx.object(this.consts.pythFeeder.feeder[token]),
|
|
164
|
+
symbolsValuation,
|
|
165
|
+
],
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
return symbolsValuation
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Creates both vaults and symbols valuation
|
|
173
|
+
*/
|
|
174
|
+
public valuate(tx: Transaction) {
|
|
175
|
+
const vaultsValuation = this.valuateVaults(tx)
|
|
176
|
+
const symbolsValuation = this.valuateSymbols(tx)
|
|
177
|
+
return { vaultsValuation, symbolsValuation }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Valuates the ZBTCVC market
|
|
182
|
+
*/
|
|
183
|
+
public async valuateMarket(): Promise<IZBTCVCMarketValuationInfo> {
|
|
184
|
+
const marketInfo = await this.getMarketInfo()
|
|
185
|
+
const days = 7
|
|
186
|
+
const fee = await this.getPastFee(days)
|
|
187
|
+
let zbtcvcPrice = 0
|
|
188
|
+
let value = 0
|
|
189
|
+
|
|
190
|
+
const vaultPromises = Object.keys(this.consts.zoCore.vaults).map(async (vault) => {
|
|
191
|
+
const vaultInfo = await this.getVaultInfo(vault)
|
|
192
|
+
const reservingFeeDelta = ZBTCVCDataAPI.calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000)
|
|
193
|
+
return (reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount) * (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[vault].decimals)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const symbolPromises = Object.keys(this.consts.zoCore.symbols).map(async (symbol) => {
|
|
197
|
+
const [direction, tokenId] = parseSymbolKey(symbol)
|
|
198
|
+
const symbolInfo = await this.getSymbolInfo(tokenId, direction === 'long')
|
|
199
|
+
const deltaSize = ZBTCVCDataAPI.calcDeltaSize(symbolInfo, (await this.getOraclePrice(tokenId)).getPriceUnchecked().getPriceAsNumberUnchecked())
|
|
200
|
+
const fundingFeeDelta = ZBTCVCDataAPI.calculateSymbolFundingFee(symbolInfo, symbolInfo.fundingFeeModel, (await this.getOraclePrice(tokenId)).getPriceUnchecked().getPriceAsNumberUnchecked(), marketInfo.lpSupplyWithDecimals, Date.now() / 1000)
|
|
201
|
+
return fundingFeeDelta + deltaSize
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const [vaultValues, symbolValues] = await Promise.all([Promise.all(vaultPromises), Promise.all(symbolPromises)])
|
|
205
|
+
|
|
206
|
+
value = vaultValues.reduce((acc: number, curr: number) => acc + curr, 0)
|
|
207
|
+
value += symbolValues.reduce((acc: number, curr: number) => acc + curr, 0)
|
|
208
|
+
|
|
209
|
+
zbtcvcPrice = value / marketInfo.lpSupplyWithDecimals
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
marketCap: value,
|
|
213
|
+
price: zbtcvcPrice,
|
|
214
|
+
supply: marketInfo.lpSupplyWithDecimals,
|
|
215
|
+
apr: (fee / value) * 365 / days,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Gets ZBTCVC market information
|
|
221
|
+
*/
|
|
222
|
+
public async getMarketInfo(): Promise<IZBTCVCMarketInfo> {
|
|
223
|
+
this.validateCache()
|
|
224
|
+
if (this.marketInfoCache) {
|
|
225
|
+
return this.marketInfoCache
|
|
226
|
+
}
|
|
227
|
+
const rawData = await this.provider.getObject({
|
|
228
|
+
id: this.consts.zoCore.market,
|
|
229
|
+
options: {
|
|
230
|
+
showContent: true,
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
return ZBTCVCDataAPI.parseMarketInfo(rawData)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Gets ZBTCVC vault information
|
|
238
|
+
*/
|
|
239
|
+
public async getVaultInfo(vaultToken: string): Promise<IZBTCVCVaultInfo> {
|
|
240
|
+
this.validateCache()
|
|
241
|
+
if (this.vaultInfoCache[vaultToken]) {
|
|
242
|
+
return this.vaultInfoCache[vaultToken]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const rawData = await this.provider.getDynamicFieldObject({
|
|
246
|
+
parentId: this.consts.zoCore.vaultsParent,
|
|
247
|
+
name: {
|
|
248
|
+
type: `${this.consts.zoCore.package}::market::VaultName<${this.consts.coins[vaultToken].module}>`,
|
|
249
|
+
value: { dummy_field: false },
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
return await this.parseVaultInfo(rawData)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Gets ZBTCVC symbol information
|
|
257
|
+
*/
|
|
258
|
+
public async getSymbolInfo(indexToken: string, long: boolean): Promise<IZBTCVCSymbolInfo> {
|
|
259
|
+
this.validateCache()
|
|
260
|
+
const symbol = joinSymbol(long ? 'long' : 'short', indexToken)
|
|
261
|
+
if (this.symbolInfoCache[symbol]) {
|
|
262
|
+
return this.symbolInfoCache[symbol]
|
|
263
|
+
}
|
|
264
|
+
const rawData = await this.provider.getDynamicFieldObject({
|
|
265
|
+
parentId: this.consts.zoCore.symbolsParent,
|
|
266
|
+
name: {
|
|
267
|
+
type: `${this.consts.zoCore.package}::market::SymbolName<${this.consts.coins[indexToken].module}, ${this.consts.zoCore.package}::market::${long ? 'LONG' : 'SHORT'}>`,
|
|
268
|
+
value: { dummy_field: false },
|
|
269
|
+
},
|
|
270
|
+
})
|
|
271
|
+
return await this.parseSymbolInfo(rawData, long)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
public async getPositionConfig(indexToken: string, long: boolean): Promise<IZBTCVCPositionConfig> {
|
|
275
|
+
this.validateCache()
|
|
276
|
+
const symbol = joinSymbol(long ? 'long' : 'short', indexToken)
|
|
277
|
+
if (this.positionConfigCache[symbol]) {
|
|
278
|
+
return this.positionConfigCache[symbol]
|
|
279
|
+
}
|
|
280
|
+
const rawData = await this.provider.getObject({
|
|
281
|
+
id: this.consts.zoCore.symbols[symbol].positionConfig,
|
|
282
|
+
options: {
|
|
283
|
+
showContent: true,
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
return ZBTCVCDataAPI.parsePositionConfig(rawData)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Gets ZBTCVC symbol configuration
|
|
291
|
+
*/
|
|
292
|
+
public async getSymbolConfig(indexToken: string, long: boolean): Promise<IZBTCVCSymbolConfig | null> {
|
|
293
|
+
this.validateCache()
|
|
294
|
+
try {
|
|
295
|
+
const rawData = await this.provider.getDynamicFieldObject({
|
|
296
|
+
parentId: this.consts.zoCore.market,
|
|
297
|
+
name: {
|
|
298
|
+
type: `${this.consts.zoCore.package}::market::SymbolName<${this.consts.coins[indexToken].module}, ${this.consts.zoCore.package}::market::${long ? 'LONG' : 'SHORT'}>`,
|
|
299
|
+
value: { dummy_field: false },
|
|
300
|
+
},
|
|
301
|
+
})
|
|
302
|
+
return ZBTCVCDataAPI.parseSymbolConfig(rawData)
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
// If the dynamic field doesn't exist, return null
|
|
306
|
+
console.error('Symbol Config Not Found')
|
|
307
|
+
return null
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
public async getPositionCapInfoList(owner: string): Promise<IZBTCVCPositionCapInfo[]> {
|
|
312
|
+
const positionCapInfoList: IZBTCVCPositionCapInfo[] = []
|
|
313
|
+
let cursor: string | undefined | null
|
|
314
|
+
let hasNextPage = true
|
|
315
|
+
|
|
316
|
+
while (hasNextPage) {
|
|
317
|
+
const positionCaps = await this.provider.getOwnedObjects({
|
|
318
|
+
owner,
|
|
319
|
+
filter: {
|
|
320
|
+
MoveModule: {
|
|
321
|
+
package: this.consts.zoCore.package,
|
|
322
|
+
module: 'market',
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
options: {
|
|
326
|
+
showType: true,
|
|
327
|
+
},
|
|
328
|
+
cursor,
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
for (const positionCap of positionCaps.data) {
|
|
332
|
+
if (positionCap.data?.type?.includes('PositionCap')) {
|
|
333
|
+
positionCapInfoList.push({
|
|
334
|
+
positionCapId: positionCap.data.objectId,
|
|
335
|
+
symbol0: positionCap.data.type.split('<')[1].split(',')[0].trim(),
|
|
336
|
+
symbol1: positionCap.data.type.split('<')[1].split(',')[1].split(',')[0].trim(),
|
|
337
|
+
long: positionCap.data.type.includes('LONG'),
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// If we don't want to fetch all pages or there are no more pages, break the loop
|
|
343
|
+
hasNextPage = positionCaps.hasNextPage
|
|
344
|
+
cursor = positionCaps.nextCursor
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return positionCapInfoList
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
public async getPositionInfoList(positionCapInfoList: IZBTCVCPositionCapInfo[], owner: string, batchSize = 10) {
|
|
351
|
+
const positionInfoList: IZBTCVCPositionInfo[] = []
|
|
352
|
+
|
|
353
|
+
// Process in batches of 10
|
|
354
|
+
for (let i = 0; i < positionCapInfoList.length; i += batchSize) {
|
|
355
|
+
const batch = positionCapInfoList.slice(i, i + batchSize)
|
|
356
|
+
|
|
357
|
+
await Promise.all(batch.map(async (positionCapInfo) => {
|
|
358
|
+
try {
|
|
359
|
+
const positionRaw = await this.provider.getDynamicFieldObject({
|
|
360
|
+
parentId: this.consts.zoCore.positionsParent,
|
|
361
|
+
name: {
|
|
362
|
+
type: `${this.consts.zoCore.package}::market::PositionName<${positionCapInfo.symbol0}, ${positionCapInfo.symbol1}, ${this.consts.zoCore.package}::market::${positionCapInfo.long ? 'LONG' : 'SHORT'}>`,
|
|
363
|
+
value: {
|
|
364
|
+
owner,
|
|
365
|
+
id: positionCapInfo.positionCapId,
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
})
|
|
369
|
+
positionInfoList.push(await this.parsePositionInfo(positionRaw, positionCapInfo.positionCapId))
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
// Position might have been deleted after force settlement
|
|
373
|
+
console.warn(`Failed to parse position info for position cap ID ${positionCapInfo.positionCapId}: ${error}`)
|
|
374
|
+
// Continue with next position without adding this one to the list
|
|
375
|
+
}
|
|
376
|
+
}))
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return positionInfoList.sort((a, b) => a.openTimestamp > b.openTimestamp ? 1 : -1)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
public async getOpenPositions(): Promise<IZBTCVCPositionInfo[]> {
|
|
383
|
+
let positionDynamicFields: DynamicFieldInfo[] = []
|
|
384
|
+
let _continue = true
|
|
385
|
+
let cursor
|
|
386
|
+
while (_continue) {
|
|
387
|
+
// data here will be a list of dynamic fields containing name and value
|
|
388
|
+
const { data, nextCursor, hasNextPage }
|
|
389
|
+
= await this.provider.getDynamicFields({
|
|
390
|
+
parentId: this.consts.zoCore.positionsParent,
|
|
391
|
+
cursor,
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
positionDynamicFields = positionDynamicFields.concat(data)
|
|
395
|
+
_continue = hasNextPage
|
|
396
|
+
cursor = nextCursor
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// then we query by dynamic field names and order by time
|
|
400
|
+
const positionInfoList: IZBTCVCPositionInfo[] = []
|
|
401
|
+
await Promise.all(
|
|
402
|
+
positionDynamicFields.map(async (positionDynamicField) => {
|
|
403
|
+
const positionRaw = await this.provider.getDynamicFieldObject({
|
|
404
|
+
parentId: this.consts.zoCore.positionsParent,
|
|
405
|
+
name: positionDynamicField.name,
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
if (positionRaw?.data?.content) {
|
|
409
|
+
const positionInfo = await this.parsePositionInfo(
|
|
410
|
+
positionRaw,
|
|
411
|
+
positionDynamicField.objectId,
|
|
412
|
+
)
|
|
413
|
+
if (positionInfo) {
|
|
414
|
+
positionInfoList.push(positionInfo)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}),
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
return positionInfoList
|
|
421
|
+
.filter(positionInfo => !positionInfo.closed)
|
|
422
|
+
.sort((a, b) => (a.openTimestamp > b.openTimestamp ? 1 : -1))
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
public async getOrderCapInfoList(owner: string): Promise<IZBTCVCOrderCapInfo[]> {
|
|
426
|
+
const orderCapInfoList: IZBTCVCOrderCapInfo[] = []
|
|
427
|
+
let cursor: string | undefined | null
|
|
428
|
+
let hasNextPage = true
|
|
429
|
+
|
|
430
|
+
while (hasNextPage) {
|
|
431
|
+
const orderCaps = await this.provider.getOwnedObjects({
|
|
432
|
+
owner,
|
|
433
|
+
filter: {
|
|
434
|
+
MoveModule: {
|
|
435
|
+
package: this.consts.zoCore.package,
|
|
436
|
+
module: 'market',
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
options: {
|
|
440
|
+
showType: true,
|
|
441
|
+
showContent: true,
|
|
442
|
+
},
|
|
443
|
+
cursor,
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
for (const orderCap of orderCaps.data) {
|
|
447
|
+
if (orderCap.data?.type?.includes('OrderCap')) {
|
|
448
|
+
orderCapInfoList.push({
|
|
449
|
+
orderCapId: orderCap.data.objectId,
|
|
450
|
+
symbol0: orderCap.data.type.split('<')[1].split(',')[0].trim(),
|
|
451
|
+
symbol1: orderCap.data.type.split('<')[1].split(',')[1].split(',')[0].trim(),
|
|
452
|
+
long: orderCap.data.type.includes('LONG'),
|
|
453
|
+
positionId: (orderCap.data.content as any)?.fields?.position_id,
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
hasNextPage = orderCaps.hasNextPage
|
|
459
|
+
cursor = orderCaps.nextCursor
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return orderCapInfoList
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
public async getOrderInfoList(orderCapInfoList: IZBTCVCOrderCapInfo[], owner: string, batchSize = 10) {
|
|
466
|
+
const orderInfoList: IZBTCVCOrderInfo[] = []
|
|
467
|
+
|
|
468
|
+
// Process in batches of 10
|
|
469
|
+
for (let i = 0; i < orderCapInfoList.length; i += batchSize) {
|
|
470
|
+
const batch = orderCapInfoList.slice(i, i + batchSize)
|
|
471
|
+
|
|
472
|
+
await Promise.all(batch.map(async (orderCapInfo) => {
|
|
473
|
+
try {
|
|
474
|
+
const orderRaw = await this.provider.getDynamicFieldObject({
|
|
475
|
+
parentId: this.consts.zoCore.ordersParent,
|
|
476
|
+
name: {
|
|
477
|
+
// 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
|
|
478
|
+
type: `${this.consts.zoCore.package}::market::OrderName<${orderCapInfo.symbol0}, ${orderCapInfo.symbol1}, ${this.consts.zoCore.package}::market::${orderCapInfo.long ? 'LONG' : 'SHORT'}, ${orderCapInfo.symbol0}>`,
|
|
479
|
+
value: {
|
|
480
|
+
owner,
|
|
481
|
+
id: orderCapInfo.orderCapId,
|
|
482
|
+
position_id: {
|
|
483
|
+
vec: orderCapInfo.positionId ? [orderCapInfo.positionId] : [],
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
})
|
|
488
|
+
orderInfoList.push(this.parseOrderInfo(orderRaw, orderCapInfo.orderCapId))
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
// Order might have been deleted
|
|
492
|
+
console.warn(`Failed to parse order info for order cap ID ${orderCapInfo.orderCapId}: ${error}`)
|
|
493
|
+
// Continue with next order without adding this one to the list
|
|
494
|
+
}
|
|
495
|
+
}))
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return orderInfoList.sort((a, b) => a.createdAt > b.createdAt ? 1 : -1)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
public async hasReferral(referree: string): Promise<boolean> {
|
|
502
|
+
const raw = await this.getReferralData(referree)
|
|
503
|
+
return !raw.error
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
public async getReferralData(referree: string): Promise<any> {
|
|
507
|
+
const raw = await this.provider.getDynamicFieldObject({
|
|
508
|
+
parentId: this.consts.zoCore.referralsParent,
|
|
509
|
+
name: {
|
|
510
|
+
type: 'address',
|
|
511
|
+
value: referree,
|
|
512
|
+
},
|
|
513
|
+
})
|
|
514
|
+
return raw
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Gets rebase fee model for ZBTCVC
|
|
519
|
+
*/
|
|
520
|
+
public async getRebaseFeeModel(): Promise<{ base: number, multiplier: number }> {
|
|
521
|
+
this.validateCache()
|
|
522
|
+
if (this.rebaseFeeModelCache) {
|
|
523
|
+
return this.rebaseFeeModelCache
|
|
524
|
+
}
|
|
525
|
+
const rawData = await this.provider.getObject({
|
|
526
|
+
id: this.consts.zoCore.rebaseFeeModel,
|
|
527
|
+
options: {
|
|
528
|
+
showContent: true,
|
|
529
|
+
},
|
|
530
|
+
})
|
|
531
|
+
return ZBTCVCDataAPI.parseRebaseFeeModel(rawData)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
public async fundingFeeRate(indexToken: string, long: boolean): Promise<number> {
|
|
535
|
+
const symbol = await this.getSymbolInfo(indexToken, long)
|
|
536
|
+
if (symbol.lastUpdate <= 0) {
|
|
537
|
+
return 0
|
|
538
|
+
}
|
|
539
|
+
const price = (await this.getOraclePrice(indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked()
|
|
540
|
+
const lpSupplyAmount = (await this.getMarketInfo()).lpSupplyWithDecimals
|
|
541
|
+
const model = symbol.fundingFeeModel
|
|
542
|
+
const elapsed = SECONDS_PER_EIGHT_HOUR
|
|
543
|
+
|
|
544
|
+
const deltaSize = ZBTCVCDataAPI.calcDeltaSize(symbol, price)
|
|
545
|
+
const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount
|
|
546
|
+
return ZBTCVCDataAPI.calcFundingFeeRate(model, pnlPerLp, elapsed)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
public async rebaseFeeRate(collateralToken: string, increase: boolean, amount: number): Promise<number> {
|
|
550
|
+
let vaultValue = 0
|
|
551
|
+
if (!increase && amount > 0) {
|
|
552
|
+
amount = -amount
|
|
553
|
+
}
|
|
554
|
+
const value = amount * (await this.getOraclePrice(collateralToken)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[collateralToken].decimals)
|
|
555
|
+
const vaultPromises = Object.keys(this.consts.zoCore.vaults).map(async (vault) => {
|
|
556
|
+
const vaultInfo = await this.getVaultInfo(vault)
|
|
557
|
+
const reservingFeeDelta = ZBTCVCDataAPI.calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000)
|
|
558
|
+
const res = (reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount) * (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[vault].decimals)
|
|
559
|
+
if (collateralToken === vault) {
|
|
560
|
+
vaultValue = res
|
|
561
|
+
}
|
|
562
|
+
return res
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
const vaultValues = await Promise.all(vaultPromises)
|
|
566
|
+
const totalVaultValue = vaultValues.reduce((acc, curr) => acc + curr, 0)
|
|
567
|
+
const targetRatio = Number.parseInt(
|
|
568
|
+
this.consts.zoCore.vaults[collateralToken].weight,
|
|
569
|
+
10,
|
|
570
|
+
) / Object.values(this.consts.zoCore.vaults)
|
|
571
|
+
.map(e => Number.parseInt(e.weight, 10))
|
|
572
|
+
.reduce((acc, curr) => acc + curr, 0)
|
|
573
|
+
|
|
574
|
+
return ZBTCVCDataAPI.calcRebaseFeeRate(
|
|
575
|
+
await this.getRebaseFeeModel(),
|
|
576
|
+
increase,
|
|
577
|
+
(vaultValue + value) / (totalVaultValue + value),
|
|
578
|
+
targetRatio,
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
public async reservingFeeRate(collateralToken: string, amount = 0): Promise<number> {
|
|
583
|
+
const vaultInfo = await this.getVaultInfo(collateralToken)
|
|
584
|
+
const vaultSupply = vaultInfo.liquidity + vaultInfo.reservedAmount + vaultInfo.unrealisedReservingFeeAmount + amount
|
|
585
|
+
const utilization = vaultSupply ? ((vaultInfo.reservedAmount + amount) / vaultSupply) : 0
|
|
586
|
+
return ZBTCVCDataAPI.calcReservingFeeRate(vaultInfo.reservingFeeModel, utilization, SECONDS_PER_EIGHT_HOUR)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
public async getHistory(trader: string, page: number, limit: number, orderType?: string, symbol?: string): Promise<IBaseHistoryResponse> {
|
|
590
|
+
const params = new URLSearchParams({
|
|
591
|
+
trader,
|
|
592
|
+
page: page.toString(),
|
|
593
|
+
limit: limit.toString(),
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
// Add filter parameters if provided
|
|
597
|
+
if (orderType && orderType !== 'all') {
|
|
598
|
+
params.append('orderType', orderType)
|
|
599
|
+
}
|
|
600
|
+
if (symbol && symbol !== 'all') {
|
|
601
|
+
params.append('symbol', symbol)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
params.append('lpType', 'ZBTCVC')
|
|
605
|
+
const url = `${this.apiEndpoint}/traderEvents?${params}`
|
|
606
|
+
const res = await fetch(url, {
|
|
607
|
+
method: 'GET',
|
|
608
|
+
headers: {
|
|
609
|
+
'Content-Type': 'application/json',
|
|
610
|
+
},
|
|
611
|
+
})
|
|
612
|
+
const response = await res.json() as any
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
histories: response.data?.histories || [],
|
|
616
|
+
pagination: response.data?.pagination || {
|
|
617
|
+
total: 0,
|
|
618
|
+
page: 1,
|
|
619
|
+
limit: 20,
|
|
620
|
+
pages: 0,
|
|
621
|
+
},
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Private helper methods
|
|
626
|
+
private static calcFundingFeeRate(model: IZBTCVCFundingFeeModel, pnlPerRate: number, elapsed: number): number {
|
|
627
|
+
const dailyRate = Math.min(model.multiplier * Math.abs(pnlPerRate), model.max)
|
|
628
|
+
const secondsRate = dailyRate * elapsed / SECONDS_PER_EIGHT_HOUR
|
|
629
|
+
return pnlPerRate >= 0 ? -secondsRate : secondsRate
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
private static calcAccFundingFeeRate(symbol: IZBTCVCSymbolInfo, model: IZBTCVCFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number): number {
|
|
633
|
+
if (symbol.lastUpdate > 0) {
|
|
634
|
+
const elapsed = timestamp - symbol.lastUpdate
|
|
635
|
+
if (elapsed > 0) {
|
|
636
|
+
const deltaSize = ZBTCVCDataAPI.calcDeltaSize(symbol, price)
|
|
637
|
+
const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount
|
|
638
|
+
return symbol.accFundingRate + ZBTCVCDataAPI.calcFundingFeeRate(model, pnlPerLp, elapsed)
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return symbol.accFundingRate
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
private static calculateSymbolFundingFee(symbol: IZBTCVCSymbolInfo, model: IZBTCVCFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number): number {
|
|
645
|
+
const accFundingRate = ZBTCVCDataAPI.calcAccFundingFeeRate(symbol, model, price, lpSupplyAmount, timestamp)
|
|
646
|
+
return symbol.unrealisedFundingFeeValue + (accFundingRate - symbol.accFundingRate) * symbol.openingSize
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
private static calculatePositionReserveFee(position: IZBTCVCPositionInfo, vault: IZBTCVCVaultInfo, model: IZBTCVCReservingFeeModel, timestamp: number): number {
|
|
650
|
+
const accReservingRate = ZBTCVCDataAPI.calcAccReservingFeeRate(vault, model, timestamp)
|
|
651
|
+
return position.reservingFeeAmount + (accReservingRate - vault.accReservingRate) * position.collateralAmount
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private static calcAccReservingFeeRate(vault: IZBTCVCVaultInfo, model: IZBTCVCReservingFeeModel, timestamp: number): number {
|
|
655
|
+
if (vault.lastUpdate > 0) {
|
|
656
|
+
const elapsed = timestamp - vault.lastUpdate
|
|
657
|
+
if (elapsed > 0) {
|
|
658
|
+
const utilization = ZBTCVCDataAPI.vaultUtilization(vault)
|
|
659
|
+
return vault.accReservingRate + ZBTCVCDataAPI.calcReservingFeeRate(model, utilization, elapsed)
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return vault.accReservingRate
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
private static calcRebaseFeeRate(model: IZBTCVCRebaseFeeModel, increase: boolean, ratio: number, targetRatio: number): number {
|
|
666
|
+
if ((increase && ratio <= targetRatio) || (!increase && ratio >= targetRatio)) {
|
|
667
|
+
return model.base
|
|
668
|
+
}
|
|
669
|
+
return model.base + model.multiplier * Math.abs(ratio - targetRatio)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
private static vaultUtilization(vault: IZBTCVCVaultInfo): number {
|
|
673
|
+
const supplyAmount = vault.liquidity + vault.reservedAmount + vault.unrealisedReservingFeeAmount
|
|
674
|
+
if (supplyAmount === 0) {
|
|
675
|
+
return 0
|
|
676
|
+
}
|
|
677
|
+
return vault.reservedAmount / supplyAmount
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private static calcReservingFeeRate(model: IZBTCVCReservingFeeModel, utilization: number, elapsed: number): number {
|
|
681
|
+
return model.multiplier * utilization * elapsed / SECONDS_PER_EIGHT_HOUR
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
private static calcDeltaSize(symbol: IZBTCVCSymbolInfo, price: number): number {
|
|
685
|
+
const latestSize = symbol.openingAmount / symbol.priceConfig.precision * price
|
|
686
|
+
return symbol.long ? symbol.openingSize - latestSize : latestSize - symbol.openingSize
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
private static parseMarketInfo(raw: any): IZBTCVCMarketInfo {
|
|
690
|
+
const content = raw.data.content.fields
|
|
691
|
+
|
|
692
|
+
return {
|
|
693
|
+
lpSupply: content.lp_supply.fields.value,
|
|
694
|
+
positionId: content.positions.fields.id.id,
|
|
695
|
+
vaultId: content.vaults.fields.id.id,
|
|
696
|
+
symbolId: content.symbols.fields.id.id,
|
|
697
|
+
referralId: content.referrals.fields.id.id,
|
|
698
|
+
orderId: content.orders.fields.id.id,
|
|
699
|
+
rebaseFeeModel: content.rebase_fee_model,
|
|
700
|
+
lpSupplyWithDecimals: content.lp_supply.fields.value / (10 ** ZBTCVC_TOKEN_DECIMALS),
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
private async parseVaultInfo(raw: any): Promise<IZBTCVCVaultInfo> {
|
|
705
|
+
const vaultFields = raw.data.content.fields.value.fields
|
|
706
|
+
const reservingFeeModelAddr = vaultFields.reserving_fee_model
|
|
707
|
+
const reservingFeeModelRaw = await this.provider.getObject({
|
|
708
|
+
id: reservingFeeModelAddr,
|
|
709
|
+
options: {
|
|
710
|
+
showContent: true,
|
|
711
|
+
},
|
|
712
|
+
})
|
|
713
|
+
const reservingFeeModel = ZBTCVCDataAPI.parseReservingFeeModel(reservingFeeModelRaw)
|
|
714
|
+
|
|
715
|
+
return {
|
|
716
|
+
liquidity: parseValue(vaultFields.liquidity),
|
|
717
|
+
reservedAmount: parseValue(vaultFields.reserved_amount),
|
|
718
|
+
unrealisedReservingFeeAmount: parseValue(
|
|
719
|
+
vaultFields.unrealised_reserving_fee_amount,
|
|
720
|
+
),
|
|
721
|
+
accReservingRate: parseValue(vaultFields.acc_reserving_rate),
|
|
722
|
+
enabled: vaultFields.enabled,
|
|
723
|
+
weight: parseValue(vaultFields.weight),
|
|
724
|
+
lastUpdate: parseValue(vaultFields.last_update),
|
|
725
|
+
reservingFeeModel,
|
|
726
|
+
priceConfig: {
|
|
727
|
+
maxInterval: parseValue(vaultFields.price_config.fields.max_interval),
|
|
728
|
+
maxConfidence: parseValue(vaultFields.price_config.fields.max_confidence),
|
|
729
|
+
precision: parseValue(vaultFields.price_config.fields.precision),
|
|
730
|
+
feeder: vaultFields.price_config.fields.feeder,
|
|
731
|
+
},
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private async parseSymbolInfo(raw: any, long: boolean): Promise<IZBTCVCSymbolInfo> {
|
|
736
|
+
const { objectId } = raw.data
|
|
737
|
+
const { fields } = raw.data.content.fields.value
|
|
738
|
+
const fundingFeeModelAddr = fields.funding_fee_model
|
|
739
|
+
const fundingFeeModelRaw = await this.provider.getObject({
|
|
740
|
+
id: fundingFeeModelAddr,
|
|
741
|
+
options: {
|
|
742
|
+
showContent: true,
|
|
743
|
+
},
|
|
744
|
+
})
|
|
745
|
+
const fundingFeeModel = ZBTCVCDataAPI.parseFundingFeeModel(fundingFeeModelRaw)
|
|
746
|
+
|
|
747
|
+
return {
|
|
748
|
+
objectId,
|
|
749
|
+
openingSize: parseValue(fields.opening_size),
|
|
750
|
+
openingAmount: parseValue(fields.opening_amount),
|
|
751
|
+
accFundingRate: parseValue(fields.acc_funding_rate),
|
|
752
|
+
realisedPnl: parseValue(fields.realised_pnl),
|
|
753
|
+
unrealisedFundingFeeValue: parseValue(fields.unrealised_funding_fee_value),
|
|
754
|
+
openEnabled: fields.open_enabled,
|
|
755
|
+
liquidateEnabled: fields.liquidate_enabled,
|
|
756
|
+
decreaseEnabled: fields.decrease_enabled,
|
|
757
|
+
lastUpdate: parseValue(fields.last_update),
|
|
758
|
+
fundingFeeModel,
|
|
759
|
+
long,
|
|
760
|
+
priceConfig: {
|
|
761
|
+
maxInterval: parseValue(fields.price_config.fields.max_interval),
|
|
762
|
+
maxConfidence: parseValue(fields.price_config.fields.max_confidence),
|
|
763
|
+
precision: parseValue(fields.price_config.fields.precision),
|
|
764
|
+
feeder: fields.price_config.fields.feeder,
|
|
765
|
+
},
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
private static parsePositionConfig(raw: any): IZBTCVCPositionConfig {
|
|
770
|
+
const positionConfigFields = raw.data.content.fields.inner.fields
|
|
771
|
+
|
|
772
|
+
return {
|
|
773
|
+
decreaseFeeBps: parseValue(positionConfigFields.decrease_fee_bps),
|
|
774
|
+
liquidationBonus: parseValue(positionConfigFields.liquidation_bonus),
|
|
775
|
+
liquidationThreshold: parseValue(positionConfigFields.liquidation_threshold),
|
|
776
|
+
maxLeverage: parseValue(positionConfigFields.max_leverage),
|
|
777
|
+
minHoldingDuration: parseValue(positionConfigFields.min_holding_duration),
|
|
778
|
+
openFeeBps: parseValue(positionConfigFields.open_fee_bps),
|
|
779
|
+
maxReservedMultiplier: parseValue(positionConfigFields.max_reserved_multiplier),
|
|
780
|
+
minCollateralValue: parseValue(positionConfigFields.min_collateral_value),
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
private static parseSymbolConfig(raw: any): IZBTCVCSymbolConfig {
|
|
785
|
+
const { fields } = raw.data.content
|
|
786
|
+
|
|
787
|
+
return {
|
|
788
|
+
id: fields.id.id,
|
|
789
|
+
max_opening_size: parseValue(fields.max_opening_size),
|
|
790
|
+
max_opening_size_enabled: fields.max_opening_size_enabled,
|
|
791
|
+
max_opening_size_per_position: parseValue(fields.max_opening_size_per_position),
|
|
792
|
+
max_opening_size_per_position_enabled: fields.max_opening_size_per_position_enabled,
|
|
793
|
+
instant_exit_fee_config: {
|
|
794
|
+
instant_exit_tier_1_duration_threshold: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_1_duration_threshold),
|
|
795
|
+
instant_exit_tier_1_fee_bps: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_1_fee_bps.fields.value),
|
|
796
|
+
instant_exit_tier_1_fee_enabled: fields.instant_exit_fee_config.fields.instant_exit_tier_1_fee_enabled,
|
|
797
|
+
instant_exit_tier_2_duration_threshold: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_2_duration_threshold),
|
|
798
|
+
instant_exit_tier_2_fee_bps: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_2_fee_bps.fields.value),
|
|
799
|
+
instant_exit_tier_2_fee_enabled: fields.instant_exit_fee_config.fields.instant_exit_tier_2_fee_enabled,
|
|
800
|
+
instant_exit_tier_3_duration_threshold: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_3_duration_threshold),
|
|
801
|
+
instant_exit_tier_3_fee_bps: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_3_fee_bps.fields.value),
|
|
802
|
+
instant_exit_tier_3_fee_enabled: fields.instant_exit_fee_config.fields.instant_exit_tier_3_fee_enabled,
|
|
803
|
+
},
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private async parsePositionInfo(raw: any, id_: string): Promise<IZBTCVCPositionInfo> {
|
|
808
|
+
const { content } = raw.data
|
|
809
|
+
const { fields } = content
|
|
810
|
+
const positionFields = fields.value.fields
|
|
811
|
+
const dataType = fields.name.type
|
|
812
|
+
|
|
813
|
+
const positionInfo = {
|
|
814
|
+
id: id_,
|
|
815
|
+
long: dataType.includes('::market::LONG'),
|
|
816
|
+
owner: fields.name.fields.owner,
|
|
817
|
+
version: Number.parseInt(raw.data.version, 10),
|
|
818
|
+
collateralToken: suiSymbolToSymbol(dataType.split('<')[1].split(',')[0].trim(), this.consts),
|
|
819
|
+
indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
|
|
820
|
+
collateralAmount: parseValue(positionFields.collateral),
|
|
821
|
+
positionAmount: parseValue(positionFields.position_amount),
|
|
822
|
+
reservedAmount: parseValue(positionFields.reserved),
|
|
823
|
+
positionSize: parseValue(positionFields.position_size),
|
|
824
|
+
lastFundingRate: parseValue(positionFields.last_funding_rate),
|
|
825
|
+
lastReservingRate: parseValue(positionFields.last_reserving_rate),
|
|
826
|
+
reservingFeeAmount: parseValue(positionFields.reserving_fee_amount),
|
|
827
|
+
fundingFeeValue: parseValue(positionFields.funding_fee_value),
|
|
828
|
+
closed: positionFields.closed,
|
|
829
|
+
openTimestamp: parseValue(positionFields.open_timestamp),
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
positionInfo.reservingFeeAmount = ZBTCVCDataAPI.calculatePositionReserveFee(positionInfo, await this.getVaultInfo(positionInfo.collateralToken), (await this.getVaultInfo(positionInfo.collateralToken)).reservingFeeModel, Date.now() / 1000)
|
|
833
|
+
positionInfo.fundingFeeValue = ZBTCVCDataAPI.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)
|
|
834
|
+
|
|
835
|
+
return positionInfo
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
private static calculatePositionFundingFee(position: IZBTCVCPositionInfo, symbol: IZBTCVCSymbolInfo, model: IZBTCVCFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number): number {
|
|
839
|
+
const accFundingRate = ZBTCVCDataAPI.calcAccFundingFeeRate(symbol, model, price, lpSupplyAmount, timestamp)
|
|
840
|
+
return position.fundingFeeValue + (accFundingRate - symbol.accFundingRate) * position.positionSize
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
private static parseRebaseFeeModel(raw: any): { base: number, multiplier: number } {
|
|
844
|
+
const { fields } = raw.data.content
|
|
845
|
+
|
|
846
|
+
return {
|
|
847
|
+
base: parseValue(fields.base),
|
|
848
|
+
multiplier: parseValue(fields.multiplier),
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
private static parseReservingFeeModel(raw: any): { multiplier: number } {
|
|
853
|
+
const content = raw.data.content.fields
|
|
854
|
+
return {
|
|
855
|
+
multiplier: parseValue(content.multiplier),
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
private static parseFundingFeeModel(raw: any): { multiplier: number, max: number } {
|
|
860
|
+
const content = raw.data.content.fields
|
|
861
|
+
return {
|
|
862
|
+
multiplier: parseValue(content.multiplier),
|
|
863
|
+
max: parseValue(content.max),
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
private parseOrderInfo(raw: any, capId: string): IZBTCVCOrderInfo {
|
|
868
|
+
const { content } = raw.data
|
|
869
|
+
const { fields } = content.fields.value
|
|
870
|
+
|
|
871
|
+
// Extract tokens from dataType
|
|
872
|
+
const dataType = content.type
|
|
873
|
+
|
|
874
|
+
const orderType = content.fields.value.type.includes('OpenPositionOrder') ? 'OPEN_POSITION' : 'DECREASE_POSITION'
|
|
875
|
+
|
|
876
|
+
const ret: IZBTCVCOrderInfo = {
|
|
877
|
+
id: content.fields.id.id,
|
|
878
|
+
capId,
|
|
879
|
+
executed: fields.executed,
|
|
880
|
+
owner: content.fields.name.fields.owner,
|
|
881
|
+
collateralToken: suiSymbolToSymbol(dataType.split('<')[2].split(',')[0].trim(), this.consts),
|
|
882
|
+
indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
|
|
883
|
+
feeToken: suiSymbolToSymbol(dataType.split(',')[3].split('>')[0].trim(), this.consts),
|
|
884
|
+
indexPrice: parseValue(fields.limited_index_price.fields.price || fields.limited_index_price),
|
|
885
|
+
collateralPriceThreshold: parseValue(fields.collateral_price_threshold),
|
|
886
|
+
feeAmount: BigInt(fields.fee),
|
|
887
|
+
long: dataType.includes('::market::LONG'),
|
|
888
|
+
orderType,
|
|
889
|
+
createdAt: parseValue(fields.created_at),
|
|
890
|
+
v11Order: !fields.limited_index_price.fields.price,
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (orderType === 'OPEN_POSITION') {
|
|
894
|
+
ret.openOrder = {
|
|
895
|
+
reserveAmount: BigInt(fields.reserve_amount),
|
|
896
|
+
collateralAmount: BigInt(fields.collateral),
|
|
897
|
+
openAmount: BigInt(fields.open_amount),
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
ret.decreaseOrder = {
|
|
902
|
+
decreaseAmount: BigInt(fields.decrease_amount),
|
|
903
|
+
takeProfit: fields.take_profit,
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return ret
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
private async getPastFee(days = 7): Promise<number> {
|
|
911
|
+
// TODO: Check with Sentio data
|
|
912
|
+
const url = `${this.apiEndpoint}/histories/proxy/fee?days=${days}`
|
|
913
|
+
const res = await fetch(url, {
|
|
914
|
+
method: 'GET',
|
|
915
|
+
headers: {
|
|
916
|
+
'Content-Type': 'application/json',
|
|
917
|
+
},
|
|
918
|
+
})
|
|
919
|
+
return Number.parseFloat(await res.text() || '0')
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
private static calculateVaultReservingFee(
|
|
923
|
+
vaultInfo: IZBTCVCVaultInfo,
|
|
924
|
+
reservingFeeModel: { multiplier: number },
|
|
925
|
+
currentTime: number,
|
|
926
|
+
): number {
|
|
927
|
+
const timeDelta = currentTime - vaultInfo.lastUpdate
|
|
928
|
+
const periods = Math.floor(timeDelta / SECONDS_PER_EIGHT_HOUR)
|
|
929
|
+
return vaultInfo.unrealisedReservingFeeAmount
|
|
930
|
+
+ (vaultInfo.reservedAmount * reservingFeeModel.multiplier * periods) / 1e18
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
private static parseCredential(raw: any, pool: IZBTCVCStakePool): IZBTCVCCredential {
|
|
934
|
+
const stakedAmount = BigInt(raw.data.content.fields.stake)
|
|
935
|
+
const accRewardPerShare = BigInt(
|
|
936
|
+
raw.data.content.fields.acc_reward_per_share,
|
|
937
|
+
)
|
|
938
|
+
return {
|
|
939
|
+
id: raw.data.objectId,
|
|
940
|
+
lockUntil: parseValue(raw.data.content.fields.lock_until),
|
|
941
|
+
amount: stakedAmount,
|
|
942
|
+
accRewardPerShare,
|
|
943
|
+
claimable:
|
|
944
|
+
((pool.accRewardPerShare - accRewardPerShare) * stakedAmount)
|
|
945
|
+
/ BigInt(1e18),
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
private static parseStakePool(raw: any): IZBTCVCStakePool {
|
|
950
|
+
const content = raw.data.content.fields
|
|
951
|
+
const pool = {
|
|
952
|
+
id: content.id.id,
|
|
953
|
+
enabled: content.enabled,
|
|
954
|
+
lastUpdatedTime: parseValue(content.last_updated_time),
|
|
955
|
+
stakedAmount: BigInt(content.staked_amount),
|
|
956
|
+
reward: BigInt(content.reward_vault),
|
|
957
|
+
startTime: parseValue(content.start_time),
|
|
958
|
+
endTime: parseValue(content.end_time),
|
|
959
|
+
rewardRate: BigInt(content.reward_rate),
|
|
960
|
+
accRewardPerShare: BigInt(content.acc_reward_per_share),
|
|
961
|
+
lockDuration: parseValue(content.lock_duration),
|
|
962
|
+
}
|
|
963
|
+
ZBTCVCDataAPI.refreshPool(pool, Math.floor(Date.now() / 1000))
|
|
964
|
+
return pool
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
private static refreshPool(pool: IZBTCVCStakePool, timestamp: number): void {
|
|
968
|
+
if (timestamp <= pool.lastUpdatedTime || timestamp < pool.startTime) {
|
|
969
|
+
return
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const applicableEndTime = Math.max(pool.endTime, pool.lastUpdatedTime)
|
|
973
|
+
|
|
974
|
+
const calculationEndTime = Math.min(timestamp, applicableEndTime)
|
|
975
|
+
|
|
976
|
+
if (calculationEndTime > pool.lastUpdatedTime && pool.stakedAmount > BigInt(0) && pool.rewardRate > BigInt(0)) {
|
|
977
|
+
const timeDiff = BigInt(calculationEndTime - pool.lastUpdatedTime)
|
|
978
|
+
const rewardAmount = timeDiff * pool.rewardRate
|
|
979
|
+
const rewardPerShare = (rewardAmount * BigInt(1e18)) / pool.stakedAmount
|
|
980
|
+
pool.accRewardPerShare += rewardPerShare
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
pool.lastUpdatedTime = calculationEndTime
|
|
984
|
+
}
|
|
985
|
+
}
|