zo-sdk 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.gitattributes +4 -0
  2. package/.prettierrc.js +5 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1 -0
  5. package/babel.config.js +11 -0
  6. package/dist/api.cjs +466 -0
  7. package/dist/api.d.cts +26 -0
  8. package/dist/api.d.cts.map +1 -0
  9. package/dist/api.d.mts +26 -0
  10. package/dist/api.d.mts.map +1 -0
  11. package/dist/api.mjs +462 -0
  12. package/dist/consts/deployments-mainnet.json +28 -0
  13. package/dist/consts/deployments-testnet.json +93 -0
  14. package/dist/consts/index.cjs +101 -0
  15. package/dist/consts/index.d.cts +66 -0
  16. package/dist/consts/index.d.cts.map +1 -0
  17. package/dist/consts/index.d.mts +66 -0
  18. package/dist/consts/index.d.mts.map +1 -0
  19. package/dist/consts/index.mjs +94 -0
  20. package/dist/consts/price_id_to_object_id.mainnet.json +1 -0
  21. package/dist/consts/price_id_to_object_id.testnet.json +17 -0
  22. package/dist/consts/staking/deployments-mainnet.json +12 -0
  23. package/dist/consts/staking/deployments-testnet.json +11 -0
  24. package/dist/data.cjs +844 -0
  25. package/dist/data.d.cts +221 -0
  26. package/dist/data.d.cts.map +1 -0
  27. package/dist/data.d.mts +221 -0
  28. package/dist/data.d.mts.map +1 -0
  29. package/dist/data.mjs +840 -0
  30. package/dist/index.cjs +21 -0
  31. package/dist/index.d.cts +6 -0
  32. package/dist/index.d.cts.map +1 -0
  33. package/dist/index.d.mts +6 -0
  34. package/dist/index.d.mts.map +1 -0
  35. package/dist/index.mjs +5 -0
  36. package/dist/oracle.cjs +178 -0
  37. package/dist/oracle.d.cts +24 -0
  38. package/dist/oracle.d.cts.map +1 -0
  39. package/dist/oracle.d.mts +24 -0
  40. package/dist/oracle.d.mts.map +1 -0
  41. package/dist/oracle.mjs +173 -0
  42. package/dist/utils.cjs +144 -0
  43. package/dist/utils.d.cts +46 -0
  44. package/dist/utils.d.cts.map +1 -0
  45. package/dist/utils.d.mts +46 -0
  46. package/dist/utils.d.mts.map +1 -0
  47. package/dist/utils.mjs +129 -0
  48. package/jest.config.js +7 -0
  49. package/package.json +29 -0
  50. package/src/api.ts +544 -0
  51. package/src/consts/deployments-mainnet.json +28 -0
  52. package/src/consts/deployments-testnet.json +93 -0
  53. package/src/consts/index.ts +162 -0
  54. package/src/consts/price_id_to_object_id.mainnet.json +1 -0
  55. package/src/consts/price_id_to_object_id.testnet.json +17 -0
  56. package/src/consts/staking/deployments-mainnet.json +12 -0
  57. package/src/consts/staking/deployments-testnet.json +11 -0
  58. package/src/data.ts +1059 -0
  59. package/src/index.ts +5 -0
  60. package/src/oracle.ts +161 -0
  61. package/src/utils.ts +184 -0
  62. package/tests/api.test.ts +219 -0
  63. package/tests/data.test.ts +156 -0
  64. package/tests/oracle.test.ts +33 -0
  65. package/tsconfig.json +25 -0
package/src/data.ts ADDED
@@ -0,0 +1,1059 @@
1
+ /* eslint-disable class-methods-use-this */
2
+
3
+ import type { 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 class DataAPI extends OracleAPI {
203
+ provider: SuiClient
204
+ apiEndpoint: string
205
+
206
+ private vaultInfoCache: Record<string, IVaultInfo> = {}
207
+
208
+ private symbolInfoCache: Record<string, ISymbolInfo> = {}
209
+
210
+ private marketInfoCache: IMarketInfo | null = null
211
+ private positionConfigCache: Record<string, IPositionConfig> = {}
212
+
213
+ private rebaseFeeModelCache: IRebaseFeeModel | null = null
214
+ private lastUpdate = 0
215
+
216
+ constructor(
217
+ network: Network,
218
+ provider: SuiClient | null,
219
+ apiEndpoint: string,
220
+ connectionURL: string,
221
+ ) {
222
+ super(network, provider, connectionURL)
223
+ this.provider = provider || createJsonRpcProvider(network)
224
+ this.apiEndpoint = apiEndpoint
225
+ }
226
+
227
+ validateCache = () => {
228
+ super.validateCache()
229
+ if (this.lastUpdate + 1000 * 60 * 3 < Date.now()) {
230
+ this.lastUpdate = Date.now()
231
+ this.vaultInfoCache = {}
232
+ this.symbolInfoCache = {}
233
+ this.marketInfoCache = null
234
+ this.positionConfigCache = {}
235
+ this.rebaseFeeModelCache = null
236
+ }
237
+ }
238
+
239
+ valuateVaults = (tx: Transaction) => {
240
+ const vaultsValuation = tx.moveCall({
241
+ target: `${this.consts.zoCore.package}::market::create_vaults_valuation`,
242
+ typeArguments: [`${this.consts.zoCore.package}::zlp::ZLP`],
243
+ arguments: [
244
+ tx.object(SUI_CLOCK_OBJECT_ID),
245
+ tx.object(this.consts.zoCore.market),
246
+ ],
247
+ })
248
+ for (const key of Object.keys(this.consts.zoCore.vaults)) {
249
+ const vault = this.consts.zoCore.vaults[key]
250
+ tx.moveCall({
251
+ // todo
252
+ target: `${this.consts.zoCore.package}::market::valuate_vault`,
253
+ typeArguments: [
254
+ `${this.consts.zoCore.package}::zlp::ZLP`,
255
+ this.consts.coins[key].module,
256
+ ],
257
+ arguments: [
258
+ tx.object(this.consts.zoCore.market),
259
+ tx.object(vault.reservingFeeModel),
260
+ tx.object(this.consts.pythFeeder.feeder[key]),
261
+ vaultsValuation,
262
+ ],
263
+ })
264
+ }
265
+ return vaultsValuation
266
+ }
267
+
268
+ valuateSymbols = (tx: Transaction) => {
269
+ const symbolsValuation = tx.moveCall({
270
+ target: `${this.consts.zoCore.package}::market::create_symbols_valuation`,
271
+ typeArguments: [`${this.consts.zoCore.package}::zlp::ZLP`],
272
+ arguments: [
273
+ tx.object(SUI_CLOCK_OBJECT_ID),
274
+ tx.object(this.consts.zoCore.market),
275
+ ],
276
+ })
277
+ for (const key of Object.keys(this.consts.zoCore.symbols)) {
278
+ const [direction, token] = parseSymbolKey(key)
279
+ const symbol = this.consts.zoCore.symbols[key]
280
+ tx.moveCall({
281
+ target: `${this.consts.zoCore.package}::market::valuate_symbol`,
282
+ typeArguments: [
283
+ `${this.consts.zoCore.package}::zlp::ZLP`,
284
+ this.consts.coins[token].module,
285
+ `${this.consts.zoCore.package}::market::${direction.toUpperCase()}`,
286
+ ],
287
+ arguments: [
288
+ tx.object(this.consts.zoCore.market),
289
+ tx.object(symbol.fundingFeeModel),
290
+ tx.object(this.consts.pythFeeder.feeder[token]),
291
+ symbolsValuation,
292
+ ],
293
+ })
294
+ }
295
+ return symbolsValuation
296
+ }
297
+
298
+ valuate = (tx: Transaction) => {
299
+ const vaultsValuation = this.valuateVaults(tx)
300
+ const symbolsValuation = this.valuateSymbols(tx)
301
+
302
+ return {
303
+ vaultsValuation,
304
+ symbolsValuation,
305
+ }
306
+ }
307
+
308
+ #parseMarketInfo(raw: any): IMarketInfo {
309
+ const content = raw.data.content.fields
310
+
311
+ return {
312
+ lpSupply: content.lp_supply.fields.value,
313
+ positionId: content.positions.fields.id.id,
314
+ vaultId: content.vaults.fields.id.id,
315
+ symbolId: content.symbols.fields.id.id,
316
+ referralId: content.referrals.fields.id.id,
317
+ orderId: content.orders.fields.id.id,
318
+ rebaseFeeModel: content.rebase_fee_model,
319
+ lpSupplyWithDecimals: content.lp_supply.fields.value / (10 ** ZLP_TOKEN_DECIMALS),
320
+ }
321
+ }
322
+
323
+ #parseVaultInfo = async (raw: any): Promise<IVaultInfo> => {
324
+ const vaultFields = raw.data.content.fields.value.fields
325
+ const reservingFeeModelAddr = vaultFields.reserving_fee_model
326
+ const reservingFeeModelRaw = await this.provider.getObject({
327
+ id: reservingFeeModelAddr,
328
+ options: {
329
+ showContent: true,
330
+ },
331
+ })
332
+ const reservingFeeModel = this.#parseReservingFeeModel(reservingFeeModelRaw)
333
+
334
+ return {
335
+ liquidity: parseValue(vaultFields.liquidity),
336
+ reservedAmount: parseValue(vaultFields.reserved_amount),
337
+ unrealisedReservingFeeAmount: parseValue(
338
+ vaultFields.unrealised_reserving_fee_amount,
339
+ ),
340
+ accReservingRate: parseValue(vaultFields.acc_reserving_rate),
341
+ enabled: vaultFields.enabled,
342
+ weight: parseValue(vaultFields.weight),
343
+ lastUpdate: parseValue(vaultFields.last_update),
344
+ reservingFeeModel,
345
+ priceConfig: {
346
+ maxInterval: parseValue(vaultFields.price_config.fields.max_interval),
347
+ maxConfidence: parseValue(vaultFields.price_config.fields.max_confidence),
348
+ precision: parseValue(vaultFields.price_config.fields.precision),
349
+ feeder: vaultFields.price_config.fields.feeder,
350
+ },
351
+ }
352
+ }
353
+
354
+ #parseSymbolInfo = async (raw: any, long: boolean): Promise<ISymbolInfo> => {
355
+ const { fields } = raw.data.content.fields.value
356
+ const fundingFeeModelAddr = fields.funding_fee_model
357
+ const fundingFeeModelRaw = await this.provider.getObject({
358
+ id: fundingFeeModelAddr,
359
+ options: {
360
+ showContent: true,
361
+ },
362
+ })
363
+ const fundingFeeModel = this.#parseFundingFeeModel(fundingFeeModelRaw)
364
+
365
+ return {
366
+ openingSize: parseValue(fields.opening_size),
367
+ openingAmount: parseValue(fields.opening_amount),
368
+ accFundingRate: parseValue(fields.acc_funding_rate),
369
+ realisedPnl: parseValue(fields.realised_pnl),
370
+ unrealisedFundingFeeValue: parseValue(fields.unrealised_funding_fee_value),
371
+ openEnabled: fields.open_enabled,
372
+ liquidateEnabled: fields.liquidate_enabled,
373
+ decreaseEnabled: fields.decrease_enabled,
374
+ lastUpdate: parseValue(fields.last_update),
375
+ fundingFeeModel,
376
+ long,
377
+ priceConfig: {
378
+ maxInterval: parseValue(fields.price_config.fields.max_interval),
379
+ maxConfidence: parseValue(fields.price_config.fields.max_confidence),
380
+ precision: parseValue(fields.price_config.fields.precision),
381
+ feeder: fields.price_config.fields.feeder,
382
+ },
383
+ }
384
+ }
385
+
386
+ #parseRebaseFeeModel(raw: any): IRebaseFeeModel {
387
+ const { fields } = raw.data.content
388
+
389
+ return {
390
+ base: parseValue(fields.base),
391
+ multiplier: parseValue(fields.multiplier),
392
+ }
393
+ }
394
+
395
+ #parseReservingFeeModel(raw: any): IReservingFeeModel {
396
+ const { fields } = raw.data.content
397
+
398
+ return {
399
+ multiplier: parseValue(fields.multiplier),
400
+ }
401
+ }
402
+
403
+ #parseFundingFeeModel(raw: any): IFundingFeeModel {
404
+ const { fields } = raw.data.content
405
+
406
+ return {
407
+ multiplier: parseValue(fields.multiplier),
408
+ max: parseValue(fields.max),
409
+ }
410
+ }
411
+
412
+ #parsePositionConfig(raw: any): IPositionConfig {
413
+ const positionConfigFields = raw.data.content.fields.inner.fields
414
+
415
+ return {
416
+ decreaseFeeBps: parseValue(positionConfigFields.decrease_fee_bps),
417
+ liquidationBonus: parseValue(positionConfigFields.liquidation_bonus),
418
+ liquidationThreshold: parseValue(positionConfigFields.liquidation_threshold),
419
+ maxLeverage: parseValue(positionConfigFields.max_leverage),
420
+ minHoldingDuration: parseValue(positionConfigFields.min_holding_duration),
421
+ openFeeBps: parseValue(positionConfigFields.open_fee_bps),
422
+ maxReservedMultiplier: parseValue(positionConfigFields.max_reserved_multiplier),
423
+ minCollateralValue: parseValue(positionConfigFields.min_collateral_value),
424
+ }
425
+ }
426
+
427
+ async #parsePositionInfo(raw: any, id_: string): Promise<IPositionInfo> {
428
+ const { content } = raw.data
429
+ const { fields } = content
430
+ const positionFields = fields.value.fields
431
+ const dataType = fields.name.type
432
+
433
+ const positionInfo = {
434
+ id: id_,
435
+ long: dataType.includes('::market::LONG'),
436
+ owner: fields.name.fields.owner,
437
+ version: Number.parseInt(raw.data.version, 10),
438
+ collateralToken: suiSymbolToSymbol(dataType.split('<')[1].split(',')[0].trim(), this.consts),
439
+ indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
440
+ collateralAmount: parseValue(positionFields.collateral),
441
+ positionAmount: parseValue(positionFields.position_amount),
442
+ reservedAmount: parseValue(positionFields.reserved),
443
+ positionSize: parseValue(positionFields.position_size),
444
+ lastFundingRate: parseValue(positionFields.last_funding_rate),
445
+ lastReservingRate: parseValue(positionFields.last_reserving_rate),
446
+ reservingFeeAmount: parseValue(positionFields.reserving_fee_amount),
447
+ fundingFeeValue: parseValue(positionFields.funding_fee_value),
448
+ closed: positionFields.closed,
449
+ openTimestamp: parseValue(positionFields.open_timestamp),
450
+ }
451
+
452
+ positionInfo.reservingFeeAmount = this.#calculatePositionReserveFee(positionInfo, await this.getVaultInfo(positionInfo.collateralToken), (await this.getVaultInfo(positionInfo.collateralToken)).reservingFeeModel, Date.now() / 1000)
453
+ 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)
454
+
455
+ return positionInfo
456
+ }
457
+
458
+ #parseOrderInfo(raw: any, capId: string): IOrderInfo {
459
+ const { content } = raw.data
460
+ const { fields } = content.fields.value
461
+
462
+ // Extract tokens from dataType
463
+ const dataType = content.type
464
+
465
+ const orderType = content.fields.value.type.includes('OpenPositionOrder') ? 'OPEN_POSITION' : 'DECREASE_POSITION'
466
+
467
+ const ret: IOrderInfo = {
468
+ id: content.fields.id.id,
469
+ capId,
470
+ executed: fields.executed,
471
+ owner: content.fields.name.fields.owner,
472
+ collateralToken: suiSymbolToSymbol(dataType.split('<')[2].split(',')[0].trim(), this.consts),
473
+ indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
474
+ feeToken: suiSymbolToSymbol(dataType.split(',')[3].split('>')[0].trim(), this.consts),
475
+ indexPrice: parseValue(fields.limited_index_price.fields.price || fields.limited_index_price),
476
+ collateralPriceThreshold: parseValue(fields.collateral_price_threshold),
477
+ feeAmount: BigInt(fields.fee),
478
+ long: dataType.includes('::market::LONG'),
479
+ orderType,
480
+ createdAt: parseValue(fields.created_at),
481
+ v11Order: !fields.limited_index_price.fields.price,
482
+ }
483
+
484
+ if (orderType === 'OPEN_POSITION') {
485
+ ret.openOrder = {
486
+ reserveAmount: BigInt(fields.reserve_amount),
487
+ collateralAmount: BigInt(fields.collateral),
488
+ openAmount: BigInt(fields.open_amount),
489
+ }
490
+ }
491
+ else {
492
+ ret.decreaseOrder = {
493
+ decreaseAmount: BigInt(fields.decrease_amount),
494
+ takeProfit: fields.take_profit,
495
+ }
496
+ }
497
+
498
+ return ret
499
+ }
500
+
501
+ #calcRebaseFeeRate(model: IRebaseFeeModel, increase: boolean, ratio: number, targetRatio: number): number {
502
+ if ((increase && ratio <= targetRatio) || (!increase && ratio >= targetRatio)) {
503
+ return model.base
504
+ }
505
+ return model.base + model.multiplier * Math.abs(ratio - targetRatio)
506
+ }
507
+
508
+ #calcReservingFeeRate(model: IReservingFeeModel, utilization: number, elapsed: number): number {
509
+ return model.multiplier * utilization * elapsed / SECONDS_PER_EIGHT_HOUR
510
+ }
511
+
512
+ #calcFundingFeeRate(model: IFundingFeeModel, pnlPerRate: number, elapsed: number): number {
513
+ const dailyRate = Math.min(model.multiplier * Math.abs(pnlPerRate), model.max)
514
+ const secondsRate = dailyRate * elapsed / SECONDS_PER_EIGHT_HOUR
515
+ return pnlPerRate >= 0 ? -secondsRate : secondsRate
516
+ }
517
+
518
+ #vaultUtilization(vault: IVaultInfo): number {
519
+ const supplyAmount = vault.liquidity + vault.reservedAmount + vault.unrealisedReservingFeeAmount
520
+ if (supplyAmount === 0) {
521
+ return 0
522
+ }
523
+ return vault.reservedAmount / supplyAmount
524
+ }
525
+
526
+ #calcAccReservingFeeRate(vault: IVaultInfo, model: IReservingFeeModel, timestamp: number): number {
527
+ if (vault.lastUpdate > 0) {
528
+ const elapsed = timestamp - vault.lastUpdate
529
+ if (elapsed > 0) {
530
+ const utilization = this.#vaultUtilization(vault)
531
+ return vault.accReservingRate + this.#calcReservingFeeRate(model, utilization, elapsed)
532
+ }
533
+ }
534
+ return vault.accReservingRate
535
+ }
536
+
537
+ #calcDeltaSize(symbol: ISymbolInfo, price: number): number {
538
+ const latestSize = symbol.openingAmount / symbol.priceConfig.precision * price
539
+ return symbol.long ? symbol.openingSize - latestSize : latestSize - symbol.openingSize
540
+ }
541
+
542
+ #calcAccFundingFeeRate(symbol: ISymbolInfo, model: IFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number): number {
543
+ if (symbol.lastUpdate > 0) {
544
+ const elapsed = timestamp - symbol.lastUpdate
545
+ if (elapsed > 0) {
546
+ const deltaSize = this.#calcDeltaSize(symbol, price)
547
+ const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount
548
+ return symbol.accFundingRate + this.#calcFundingFeeRate(model, pnlPerLp, elapsed)
549
+ }
550
+ }
551
+ return symbol.accFundingRate
552
+ }
553
+
554
+ #calculateSymbolFundingFee(symbol: ISymbolInfo, model: IFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number): number {
555
+ const accFundingRate = this.#calcAccFundingFeeRate(symbol, model, price, lpSupplyAmount, timestamp)
556
+ return symbol.unrealisedFundingFeeValue + (accFundingRate - symbol.accFundingRate) * symbol.openingSize
557
+ }
558
+
559
+ #calculatePositionFundingFee(position: IPositionInfo, symbol: ISymbolInfo, model: IFundingFeeModel, price: number, lpSupplyAmount: number, timestamp: number): number {
560
+ const accFundingRate = this.#calcAccFundingFeeRate(symbol, model, price, lpSupplyAmount, timestamp)
561
+ return position.fundingFeeValue + (accFundingRate - symbol.accFundingRate) * position.positionSize
562
+ }
563
+
564
+ #calculateVaultReservingFee(vault: IVaultInfo, model: IReservingFeeModel, timestamp: number): number {
565
+ const accReservingRate = this.#calcAccReservingFeeRate(vault, model, timestamp)
566
+ return vault.unrealisedReservingFeeAmount + (accReservingRate - vault.accReservingRate) * vault.reservedAmount
567
+ }
568
+
569
+ #calculatePositionReserveFee(position: IPositionInfo, vault: IVaultInfo, model: IReservingFeeModel, timestamp: number): number {
570
+ const accReservingRate = this.#calcAccReservingFeeRate(vault, model, timestamp)
571
+ return position.reservingFeeAmount + (accReservingRate - vault.accReservingRate) * position.collateralAmount
572
+ }
573
+
574
+ public async getPastFee(days = 7) {
575
+ // FIXME: Use Sentio data
576
+ const url = `${this.apiEndpoint}/histories/proxy/fee?days=${days}`
577
+ const res = await fetch(url, {
578
+ method: 'GET',
579
+ headers: {
580
+ 'Content-Type': 'application/json',
581
+ },
582
+ })
583
+ return Number.parseFloat(await res.text() || '0')
584
+ }
585
+
586
+ public async valuateMarket(): Promise<IMarketValuationInfo> {
587
+ const marketInfo = await this.getMarketInfo()
588
+ const days = 7
589
+ const fee = await this.getPastFee(days)
590
+ let zlpPrice = 0
591
+ let value = 0
592
+
593
+ const vaultPromises = Object.keys(this.consts.zoCore.vaults).map(async (vault) => {
594
+ const vaultInfo = await this.getVaultInfo(vault)
595
+ const reservingFeeDelta = this.#calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000)
596
+ return (reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount) * (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[vault].decimals)
597
+ })
598
+
599
+ const symbolPromises = Object.keys(this.consts.zoCore.symbols).map(async (symbol) => {
600
+ const [direction, tokenId] = parseSymbolKey(symbol)
601
+ const symbolInfo = await this.getSymbolInfo(tokenId, direction === 'long')
602
+ const deltaSize = this.#calcDeltaSize(symbolInfo, (await this.getOraclePrice(tokenId)).getPriceUnchecked().getPriceAsNumberUnchecked())
603
+ const fundingFeeDelta = this.#calculateSymbolFundingFee(symbolInfo, symbolInfo.fundingFeeModel, (await this.getOraclePrice(tokenId)).getPriceUnchecked().getPriceAsNumberUnchecked(), marketInfo.lpSupplyWithDecimals, Date.now() / 1000)
604
+ return fundingFeeDelta + deltaSize
605
+ })
606
+
607
+ const [vaultValues, symbolValues] = await Promise.all([Promise.all(vaultPromises), Promise.all(symbolPromises)])
608
+
609
+ value = vaultValues.reduce((acc, curr) => acc + curr, 0)
610
+ value += symbolValues.reduce((acc, curr) => acc + curr, 0)
611
+
612
+ zlpPrice = value / marketInfo.lpSupplyWithDecimals
613
+
614
+ return {
615
+ marketCap: value,
616
+ zlpPrice,
617
+ zlpSupply: marketInfo.lpSupplyWithDecimals,
618
+ apr: (fee / value) * 365 / days,
619
+ }
620
+ }
621
+
622
+ public async getMarketInfo() {
623
+ this.validateCache()
624
+ if (this.marketInfoCache) {
625
+ return this.marketInfoCache
626
+ }
627
+ const rawData = await this.provider.getObject({
628
+ id: this.consts.zoCore.market,
629
+ options: {
630
+ showContent: true,
631
+ },
632
+ })
633
+ return this.#parseMarketInfo(rawData)
634
+ }
635
+
636
+ public async getVaultInfo(vaultToken: string) {
637
+ this.validateCache()
638
+ if (this.vaultInfoCache[vaultToken]) {
639
+ return this.vaultInfoCache[vaultToken]
640
+ }
641
+
642
+ const rawData = await this.provider.getDynamicFieldObject({
643
+ parentId: this.consts.zoCore.vaultsParent,
644
+ name: {
645
+ type: `${this.consts.zoCore.package}::market::VaultName<${this.consts.coins[vaultToken].module}>`,
646
+ value: { dummy_field: false },
647
+ },
648
+ })
649
+ return await this.#parseVaultInfo(rawData)
650
+ }
651
+
652
+ public async getSymbolInfo(indexToken: string, long: boolean) {
653
+ this.validateCache()
654
+ const symbol = joinSymbol(long ? 'long' : 'short', indexToken)
655
+ if (this.symbolInfoCache[symbol]) {
656
+ return this.symbolInfoCache[symbol]
657
+ }
658
+ const rawData = await this.provider.getDynamicFieldObject({
659
+ parentId: this.consts.zoCore.symbolsParent,
660
+ name: {
661
+ type: `${this.consts.zoCore.package}::market::SymbolName<${this.consts.coins[indexToken].module}, ${this.consts.zoCore.package}::market::${long ? 'LONG' : 'SHORT'}>`,
662
+ value: { dummy_field: false },
663
+ },
664
+ })
665
+ return await this.#parseSymbolInfo(rawData, long)
666
+ }
667
+
668
+ public async getPositionConfig(indexToken: string, long: boolean) {
669
+ this.validateCache()
670
+ const symbol = joinSymbol(long ? 'long' : 'short', indexToken)
671
+ if (this.positionConfigCache[symbol]) {
672
+ return this.positionConfigCache[symbol]
673
+ }
674
+ const rawData = await this.provider.getObject({
675
+ id: this.consts.zoCore.symbols[symbol].positionConfig,
676
+ options: {
677
+ showContent: true,
678
+ },
679
+ })
680
+ return this.#parsePositionConfig(rawData)
681
+ }
682
+
683
+ public async getPositionCapInfoList(owner: string): Promise<IPositionCapInfo[]> {
684
+ const positionCaps = await this.provider.getOwnedObjects({
685
+ owner,
686
+ filter: {
687
+ MoveModule: {
688
+ package: this.consts.zoCore.package,
689
+ module: 'market',
690
+ },
691
+ },
692
+ options: {
693
+ showType: true,
694
+ },
695
+ })
696
+ const positionCapInfoList = []
697
+ for (const positionCap of positionCaps.data) {
698
+ if (positionCap.data?.type?.includes('PositionCap')) {
699
+ positionCapInfoList.push({
700
+ positionCapId: positionCap.data.objectId,
701
+ symbol0: positionCap.data.type.split('<')[1].split(',')[0].trim(),
702
+ symbol1: positionCap.data.type.split('<')[1].split(',')[1].split(',')[0].trim(),
703
+ long: positionCap.data.type.includes('LONG'),
704
+ })
705
+ }
706
+ }
707
+ return positionCapInfoList
708
+ }
709
+
710
+ public async getPositionInfoList(positionCapInfoList: IPositionCapInfo[], owner: string) {
711
+ const positionInfoList: IPositionInfo[] = []
712
+ await Promise.all(positionCapInfoList.map(async (positionCapInfo) => {
713
+ const positionRaw = await this.provider.getDynamicFieldObject({
714
+ parentId: this.consts.zoCore.positionsParent,
715
+ name: {
716
+ type: `${this.consts.zoCore.package}::market::PositionName<${positionCapInfo.symbol0}, ${positionCapInfo.symbol1}, ${this.consts.zoCore.package}::market::${positionCapInfo.long ? 'LONG' : 'SHORT'}>`,
717
+ value: {
718
+ owner,
719
+ id: positionCapInfo.positionCapId,
720
+ },
721
+ },
722
+ })
723
+ positionInfoList.push(await this.#parsePositionInfo(positionRaw, positionCapInfo.positionCapId))
724
+ }))
725
+
726
+ return positionInfoList.sort((a, b) => a.openTimestamp > b.openTimestamp ? 1 : -1)
727
+ }
728
+
729
+ public async getOrderCapInfoList(owner: string) {
730
+ const orderCaps = await this.provider.getOwnedObjects({
731
+ owner,
732
+ filter: {
733
+ MoveModule: {
734
+ package: this.consts.zoCore.package,
735
+ module: 'market',
736
+ },
737
+ },
738
+ options: {
739
+ showType: true,
740
+ showContent: true,
741
+ },
742
+ })
743
+ const orderCapInfoList = []
744
+ for (const orderCap of orderCaps.data) {
745
+ if (orderCap.data?.type?.includes('OrderCap')) {
746
+ orderCapInfoList.push({
747
+ orderCapId: orderCap.data.objectId,
748
+ symbol0: orderCap.data.type.split('<')[1].split(',')[0].trim(),
749
+ symbol1: orderCap.data.type.split('<')[1].split(',')[1].split(',')[0].trim(),
750
+ long: orderCap.data.type.includes('LONG'),
751
+ positionId: (orderCap.data.content as any)?.fields?.position_id,
752
+ })
753
+ }
754
+ }
755
+ return orderCapInfoList
756
+ }
757
+
758
+ public async getOrderInfoList(orderCapInfoList: IOrderCapInfo[], owner: string) {
759
+ const orderInfoList: IOrderInfo[] = []
760
+ await Promise.all(orderCapInfoList.map(async (orderCapInfo) => {
761
+ const orderRaw = await this.provider.getDynamicFieldObject({
762
+ parentId: this.consts.zoCore.ordersParent,
763
+ name: {
764
+ type: `${this.consts.zoCore.package}::market::OrderName<${orderCapInfo.symbol0}, ${orderCapInfo.symbol1}, ${this.consts.zoCore.package}::market::${orderCapInfo.long ? 'LONG' : 'SHORT'}, ${this.consts.coins.sui.module}>`,
765
+ value: {
766
+ owner,
767
+ id: orderCapInfo.orderCapId,
768
+ position_id: {
769
+ vec: orderCapInfo.positionId ? [orderCapInfo.positionId] : [],
770
+ },
771
+ },
772
+ },
773
+ })
774
+ orderInfoList.push(this.#parseOrderInfo(orderRaw, orderCapInfo.orderCapId))
775
+ }))
776
+ return orderInfoList.sort((a, b) => a.createdAt > b.createdAt ? 1 : -1)
777
+ }
778
+
779
+ public async getRebaseFeeModel() {
780
+ this.validateCache()
781
+ if (this.rebaseFeeModelCache) {
782
+ return this.rebaseFeeModelCache
783
+ }
784
+ const rawData = await this.provider.getObject({
785
+ id: this.consts.zoCore.rebaseFeeModel,
786
+ options: {
787
+ showContent: true,
788
+ },
789
+ })
790
+ return this.#parseRebaseFeeModel(rawData)
791
+ }
792
+
793
+ fundingFeeRate = async (indexToken: string, long: boolean) => {
794
+ const symbol = await this.getSymbolInfo(indexToken, long)
795
+ if (symbol.lastUpdate <= 0) {
796
+ return 0
797
+ }
798
+ const price = (await this.getOraclePrice(indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked()
799
+ const lpSupplyAmount = (await this.getMarketInfo()).lpSupplyWithDecimals
800
+ const model = symbol.fundingFeeModel
801
+ const elapsed = SECONDS_PER_EIGHT_HOUR
802
+
803
+ const deltaSize = this.#calcDeltaSize(symbol, price)
804
+ const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount
805
+ return this.#calcFundingFeeRate(model, pnlPerLp, elapsed)
806
+ }
807
+
808
+ rebaseFeeRate = async (collateralToken: string, increase: boolean, amount: number) => {
809
+ let vaultValue = 0
810
+ if (!increase && amount > 0) {
811
+ amount = -amount
812
+ }
813
+ const value = amount * (await this.getOraclePrice(collateralToken)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[collateralToken].decimals)
814
+ const vaultPromises = Object.keys(this.consts.zoCore.vaults).map(async (vault) => {
815
+ const vaultInfo = await this.getVaultInfo(vault)
816
+ const reservingFeeDelta = this.#calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000)
817
+ const res = (reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount) * (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[vault].decimals)
818
+ if (collateralToken === vault) {
819
+ vaultValue = res
820
+ }
821
+ return res
822
+ })
823
+
824
+ const vaultValues = await Promise.all(vaultPromises)
825
+ const totalVaultValue = vaultValues.reduce((acc, curr) => acc + curr, 0)
826
+ const targetRatio = Number.parseInt(
827
+ this.consts.zoCore.vaults[collateralToken].weight,
828
+ 10,
829
+ ) / Object.values(this.consts.zoCore.vaults)
830
+ .map(e => Number.parseInt(e.weight, 10))
831
+ .reduce((acc, curr) => acc + curr, 0)
832
+
833
+ return this.#calcRebaseFeeRate(
834
+ await this.getRebaseFeeModel(),
835
+ increase,
836
+ (vaultValue + value) / (totalVaultValue + value),
837
+ targetRatio,
838
+ )
839
+ }
840
+
841
+ reservingFeeRate = async (collateralToken: string, amount = 0) => {
842
+ const vaultInfo = await this.getVaultInfo(collateralToken)
843
+ const vaultSupply = vaultInfo.liquidity + vaultInfo.reservedAmount + vaultInfo.unrealisedReservingFeeAmount + amount
844
+ const utilization = vaultSupply ? ((vaultInfo.reservedAmount + amount) / vaultSupply) : 0
845
+ return this.#calcReservingFeeRate(vaultInfo.reservingFeeModel, utilization, SECONDS_PER_EIGHT_HOUR)
846
+ }
847
+
848
+ getProxiedHistories = async (trader: string): Promise<IHistory[]> => {
849
+ const url = `${this.apiEndpoint}/traderEvents?trader=${trader}`;
850
+ const res = await fetch(url, {
851
+ method: 'GET',
852
+ headers: {
853
+ 'Content-Type': 'application/json',
854
+ },
855
+ });
856
+ const historyInfoList = await res.json();
857
+ return historyInfoList;
858
+ }
859
+
860
+ getStaked = async (owner: string): Promise<IStaked> => {
861
+ const rawCredentials = await this.provider.getOwnedObjects({
862
+ owner,
863
+ filter: {
864
+ MoveModule: {
865
+ package: this.consts.zoStaking.package,
866
+ module: 'pool',
867
+ },
868
+ },
869
+ options: {
870
+ showType: true,
871
+ showContent: true,
872
+ },
873
+ })
874
+ const pool = await this.getStakePool()
875
+ const credentials = rawCredentials.data.map((item: any) => this.#parseCredential(item, pool))
876
+ return {
877
+ credentials,
878
+ amount: credentials.reduce((acc: bigint, cur: ICredential) => acc + cur.amount, BigInt(0)),
879
+ claimable: credentials.reduce((acc: bigint, cur: ICredential) => acc + cur.claimable, BigInt(0)),
880
+ }
881
+ }
882
+
883
+ getStakePool = async (): Promise<IStakePool> => {
884
+ const raw = await this.provider.getObject({
885
+ id: this.consts.zoStaking.pool,
886
+ options: {
887
+ showContent: true,
888
+ },
889
+ })
890
+ return this.#parseStakePool(raw)
891
+ }
892
+
893
+ #parseStakePool(raw: any): IStakePool {
894
+ const content = raw.data.content.fields
895
+ const pool = {
896
+ id: content.id.id,
897
+ enabled: content.enabled,
898
+ lastUpdatedTime: parseValue(content.last_updated_time),
899
+ stakedAmount: BigInt(content.staked_amount),
900
+ reward: BigInt(content.reward),
901
+ startTime: parseValue(content.start_time),
902
+ endTime: parseValue(content.end_time),
903
+ accRewardPerShare: BigInt(content.acc_reward_per_share),
904
+ lockDuration: parseValue(content.lock_duration),
905
+ }
906
+ this.#refreshPool(pool, Math.floor(Date.now() / 1000))
907
+ return pool
908
+ }
909
+
910
+ #parseCredential(raw: any, pool: IStakePool): ICredential {
911
+ const stakedAmount = BigInt(raw.data.content.fields.stake)
912
+ const accRewardPerShare = BigInt(raw.data.content.fields.acc_reward_per_share)
913
+ return {
914
+ id: raw.data.objectId,
915
+ lockUntil: parseValue(raw.data.content.fields.lock_until),
916
+ amount: stakedAmount,
917
+ accRewardPerShare,
918
+ claimable: ((pool.accRewardPerShare - accRewardPerShare) * stakedAmount) / BigInt(1e18),
919
+ }
920
+ }
921
+
922
+ #refreshPool(pool: IStakePool, timestamp: number): void {
923
+ if (timestamp === pool.lastUpdatedTime || timestamp < pool.startTime) {
924
+ return
925
+ }
926
+ if (pool.lastUpdatedTime === pool.endTime || pool.stakedAmount === BigInt(0)) {
927
+ return
928
+ }
929
+ if (timestamp > pool.endTime) {
930
+ timestamp = pool.endTime
931
+ }
932
+
933
+ const rewardAmount = pool.reward * BigInt(timestamp - pool.lastUpdatedTime)
934
+ / BigInt(pool.endTime - pool.lastUpdatedTime)
935
+
936
+ pool.lastUpdatedTime = timestamp
937
+ const rewardPerShare = rewardAmount * BigInt(1e18) / pool.stakedAmount
938
+ pool.accRewardPerShare += rewardPerShare
939
+ }
940
+
941
+ async getReferralData(referree: string): Promise<any> {
942
+ const raw = await this.provider.getDynamicFieldObject({
943
+ parentId: this.consts.zoCore.referralsParent,
944
+ name: {
945
+ type: 'address',
946
+ value: referree,
947
+ },
948
+ })
949
+ return raw
950
+ }
951
+
952
+ async hasReferral(referree: string): Promise<boolean> {
953
+ const raw = await this.getReferralData(referree)
954
+ return !raw.error
955
+ }
956
+
957
+ async getReferreeFromReferrer(referrer: string): Promise<any> {
958
+ const url = new URL(`${this.apiEndpoint}/executions`)
959
+ const data = {
960
+ projectId: `${this.network === 'mainnet' ? 'nCBQRfhN' : 'C5iFbF6X'}`,
961
+ projectSlug: `zo-${this.network}`,
962
+ projectOwner: 'zo',
963
+ sqlQuery: {
964
+ sql: `SELECT referralSender, timestamp from \`Referral_Added\` WHERE referralReferrer = '${referrer}'`,
965
+ size: 100,
966
+ },
967
+ }
968
+ const res = await fetch(url, {
969
+ method: 'POST',
970
+ headers: {
971
+ 'Content-Type': 'application/json',
972
+ },
973
+ body: JSON.stringify(data),
974
+ })
975
+ return (await res.json()).result.rows.map((e: any) => ({ address: e.referralSender, date: new Date(e.timestamp) }))
976
+ }
977
+
978
+ async getReferreeDetail(referree: string): Promise<any> {
979
+ const url = new URL(`${this.apiEndpoint}/executions`)
980
+ const data = {
981
+ projectId: `${this.network === 'mainnet' ? 'nCBQRfhN' : 'C5iFbF6X'}`,
982
+ projectSlug: `zo-${this.network}`,
983
+ projectOwner: 'zo',
984
+ sqlQuery: {
985
+ sql: `SELECT sum(volume) as volumeUsd, sum(rebate) as rebateUsd FROM \`User_Interaction\` WHERE distinct_id = '${referree}' and rebate != 0`,
986
+ size: 100,
987
+ },
988
+ }
989
+ const res = await fetch(url, {
990
+ method: 'POST',
991
+ headers: {
992
+ 'Content-Type': 'application/json',
993
+ },
994
+ body: JSON.stringify(data),
995
+ })
996
+ const row = (await res.json()).result.rows[0]
997
+ return {
998
+ volumeUsd: Number.parseFloat(row.volumeUsd),
999
+ rebateUsd: Number.parseFloat(row.rebateUsd),
1000
+ }
1001
+ }
1002
+
1003
+ async getReferrerDetail(referrer: string): Promise<any> {
1004
+ const url = new URL(`${this.apiEndpoint}/executions`)
1005
+ const data = {
1006
+ projectId: `${this.network === 'mainnet' ? 'nCBQRfhN' : 'C5iFbF6X'}`,
1007
+ projectSlug: `zo-${this.network}`,
1008
+ projectOwner: 'zo',
1009
+ sqlQuery: {
1010
+ sql: `SELECT sum(volume) as volumeUsd, sum(rebate) as rebateUsd FROM \`User_Interaction\` WHERE referralReceiver = '${referrer}' and rebate != 0`,
1011
+ size: 100,
1012
+ },
1013
+ }
1014
+ const res = await fetch(url, {
1015
+ method: 'POST',
1016
+ headers: {
1017
+ 'Content-Type': 'application/json',
1018
+ },
1019
+ body: JSON.stringify(data),
1020
+ })
1021
+ const row = (await res.json()).result.rows[0]
1022
+ return {
1023
+ volumeUsd: Number.parseFloat(row.volumeUsd),
1024
+ rebateUsd: Number.parseFloat(row.rebateUsd),
1025
+ }
1026
+ }
1027
+
1028
+ async getReferralTxs(referrer: string): Promise<any> {
1029
+ const url = new URL(`${this.apiEndpoint}/executions`)
1030
+ const data = {
1031
+ projectId: `${this.network === 'mainnet' ? 'nCBQRfhN' : 'C5iFbF6X'}`,
1032
+ projectSlug: `zo-${this.network}`,
1033
+ projectOwner: 'zo',
1034
+ sqlQuery: {
1035
+ sql: `SELECT distinct_id, volume, rebate, timestamp, transaction_hash FROM \`User_Interaction\` WHERE referralReceiver = '${referrer}' and rebate != 0 order by timestamp desc`,
1036
+ size: 100,
1037
+ },
1038
+ }
1039
+ const res = await fetch(url, {
1040
+ method: 'POST',
1041
+ headers: {
1042
+ 'Content-Type': 'application/json',
1043
+ },
1044
+ body: JSON.stringify(data),
1045
+ })
1046
+
1047
+ const { rows } = (await res.json()).result
1048
+
1049
+ return rows.map((e: any) => {
1050
+ return {
1051
+ referree: e.distinct_id,
1052
+ volume: Number.parseFloat(e.volume),
1053
+ rebate: Number.parseFloat(e.rebate),
1054
+ date: new Date(e.timestamp),
1055
+ tx: e.transaction_hash,
1056
+ }
1057
+ })
1058
+ }
1059
+ }