@zofai/zo-sdk 0.1.92 → 0.1.94

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 (54) hide show
  1. package/dist/consts/deployments-slp-mainnet.json +1 -1
  2. package/dist/consts/deployments-usdz-mainnet.json +1 -1
  3. package/dist/consts/deployments-zlp-mainnet.json +1 -1
  4. package/dist/implementations/SLPDataAPI.cjs +227 -51
  5. package/dist/implementations/SLPDataAPI.cjs.map +1 -1
  6. package/dist/implementations/SLPDataAPI.d.cts +8 -1
  7. package/dist/implementations/SLPDataAPI.d.cts.map +1 -1
  8. package/dist/implementations/SLPDataAPI.d.mts +8 -1
  9. package/dist/implementations/SLPDataAPI.d.mts.map +1 -1
  10. package/dist/implementations/SLPDataAPI.mjs +227 -51
  11. package/dist/implementations/SLPDataAPI.mjs.map +1 -1
  12. package/dist/implementations/USDZDataAPI.cjs +208 -48
  13. package/dist/implementations/USDZDataAPI.cjs.map +1 -1
  14. package/dist/implementations/USDZDataAPI.d.cts +8 -1
  15. package/dist/implementations/USDZDataAPI.d.cts.map +1 -1
  16. package/dist/implementations/USDZDataAPI.d.mts +8 -1
  17. package/dist/implementations/USDZDataAPI.d.mts.map +1 -1
  18. package/dist/implementations/USDZDataAPI.mjs +208 -48
  19. package/dist/implementations/USDZDataAPI.mjs.map +1 -1
  20. package/dist/implementations/ZLPDataAPI.cjs +211 -50
  21. package/dist/implementations/ZLPDataAPI.cjs.map +1 -1
  22. package/dist/implementations/ZLPDataAPI.d.cts +8 -1
  23. package/dist/implementations/ZLPDataAPI.d.cts.map +1 -1
  24. package/dist/implementations/ZLPDataAPI.d.mts +8 -1
  25. package/dist/implementations/ZLPDataAPI.d.mts.map +1 -1
  26. package/dist/implementations/ZLPDataAPI.mjs +211 -50
  27. package/dist/implementations/ZLPDataAPI.mjs.map +1 -1
  28. package/dist/interfaces/base.d.cts +22 -0
  29. package/dist/interfaces/base.d.cts.map +1 -1
  30. package/dist/interfaces/base.d.mts +22 -0
  31. package/dist/interfaces/base.d.mts.map +1 -1
  32. package/dist/interfaces/slp.d.cts +8 -1
  33. package/dist/interfaces/slp.d.cts.map +1 -1
  34. package/dist/interfaces/slp.d.mts +8 -1
  35. package/dist/interfaces/slp.d.mts.map +1 -1
  36. package/dist/interfaces/usdz.d.cts +8 -1
  37. package/dist/interfaces/usdz.d.cts.map +1 -1
  38. package/dist/interfaces/usdz.d.mts +8 -1
  39. package/dist/interfaces/usdz.d.mts.map +1 -1
  40. package/dist/interfaces/zlp.d.cts +8 -1
  41. package/dist/interfaces/zlp.d.cts.map +1 -1
  42. package/dist/interfaces/zlp.d.mts +8 -1
  43. package/dist/interfaces/zlp.d.mts.map +1 -1
  44. package/package.json +4 -1
  45. package/src/consts/deployments-slp-mainnet.json +1 -1
  46. package/src/consts/deployments-usdz-mainnet.json +1 -1
  47. package/src/consts/deployments-zlp-mainnet.json +1 -1
  48. package/src/implementations/SLPDataAPI.ts +253 -23
  49. package/src/implementations/USDZDataAPI.ts +232 -20
  50. package/src/implementations/ZLPDataAPI.ts +233 -21
  51. package/src/interfaces/base.ts +26 -0
  52. package/src/interfaces/slp.ts +10 -1
  53. package/src/interfaces/usdz.ts +10 -1
  54. package/src/interfaces/zlp.ts +10 -1
@@ -15,6 +15,7 @@ import type {
15
15
  IBaseHistoryResponse,
16
16
  IBaseOrderType,
17
17
  IBaseStaked,
18
+ ISwapFeeBreakdown,
18
19
  IUSDZCredential,
19
20
  IUSDZDataAPI,
20
21
  IUSDZFundingFeeModel,
@@ -37,6 +38,20 @@ import type {
37
38
  } from '../interfaces'
38
39
  import { joinSymbol, parseSymbolKey, parseValue, suiSymbolToSymbol } from '../utils'
39
40
 
41
+ interface SwapImpactConfig {
42
+ id: string
43
+ enabled: boolean
44
+ impactMultiplier: number
45
+ maxImpactRate: number
46
+ }
47
+
48
+ interface EmaVolatilityFeeConfig {
49
+ id: string
50
+ enabled: boolean
51
+ multiplier: number
52
+ maxFeeRate: number
53
+ }
54
+
40
55
  export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
41
56
  constructor(
42
57
  network: Network,
@@ -196,11 +211,17 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
196
211
  let usdzPrice = 0
197
212
  let value = 0
198
213
 
199
- const vaultPromises = Object.keys(this.consts.zoCore.vaults).map(async (vault) => {
214
+ const vaultKeys = Object.keys(this.consts.zoCore.vaults)
215
+ const vaultData = await Promise.all(vaultKeys.map(async (vault) => {
200
216
  const vaultInfo = await this.getVaultInfo(vault)
201
217
  const reservingFeeDelta = USDZDataAPI.calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000)
202
- return (reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount) * (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[vault].decimals)
203
- })
218
+ const totalVaultAmount = reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount
219
+ const oraclePrice = (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked()
220
+ const vaultValue = totalVaultAmount * oraclePrice / (10 ** this.consts.coins[vault].decimals)
221
+ return { vault, oraclePrice, vaultValue }
222
+ }))
223
+ const vaultPrices = Object.fromEntries(vaultData.map(d => [d.vault, d.oraclePrice]))
224
+ const vaultValues = vaultData.map(d => d.vaultValue)
204
225
 
205
226
  const symbolPromises = Object.keys(this.consts.zoCore.symbols).map(async (symbol) => {
206
227
  const [direction, tokenId] = parseSymbolKey(symbol)
@@ -220,14 +241,14 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
220
241
  (await this.getOraclePrice(tokenId)).getPriceUnchecked().getPriceAsNumberUnchecked(),
221
242
  marketInfo.lpSupplyWithDecimals,
222
243
  Date.now() / 1000,
223
- oiState && oiState.enabled ? oiState.model : undefined,
244
+ oiState && oiState.enabled ? oiState : undefined,
224
245
  pairedSymbol.openingSize,
225
246
  )
226
247
 
227
248
  return fundingFeeDelta + deltaSize
228
249
  })
229
250
 
230
- const [vaultValues, symbolValues] = await Promise.all([Promise.all(vaultPromises), Promise.all(symbolPromises)])
251
+ const symbolValues = await Promise.all(symbolPromises)
231
252
 
232
253
  value = vaultValues.reduce((acc: number, curr: number) => acc + curr, 0)
233
254
  value += symbolValues.reduce((acc: number, curr: number) => acc + curr, 0)
@@ -239,6 +260,7 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
239
260
  price: usdzPrice,
240
261
  supply: marketInfo.lpSupplyWithDecimals,
241
262
  apr: Number(marketInfo.apr),
263
+ vaultPrices,
242
264
  }
243
265
  }
244
266
 
@@ -378,6 +400,164 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
378
400
  }
379
401
  }
380
402
 
403
+ public async calculateSwapFeeBreakdown(fromToken: string, toToken: string, fromAmount: number): Promise<ISwapFeeBreakdown> {
404
+ const timestamp = Date.now() / 1000
405
+
406
+ const fromDecimals = this.consts.coins[fromToken]?.decimals
407
+ const toDecimals = this.consts.coins[toToken]?.decimals
408
+ if (fromDecimals === undefined || toDecimals === undefined) {
409
+ throw new Error(`Unknown token decimals for swap: ${fromToken} -> ${toToken}`)
410
+ }
411
+
412
+ const fromFeed = await this.getOraclePrice(fromToken)
413
+ const toFeed = await this.getOraclePrice(toToken)
414
+ const fromPrice = fromFeed.getPriceUnchecked().getPriceAsNumberUnchecked()
415
+ const toPrice = toFeed.getPriceUnchecked().getPriceAsNumberUnchecked()
416
+
417
+ const swapValue = (fromAmount * fromPrice) / (10 ** fromDecimals)
418
+ const totalVaultsValue = await this.#getTotalVaultsValueUsd(timestamp)
419
+
420
+ const rebaseFeeInRate = await this.rebaseFeeRate(fromToken, true, fromAmount)
421
+ const rebaseFeeInValue = swapValue * rebaseFeeInRate
422
+
423
+ const estimatedToAmount = toPrice !== 0
424
+ ? (swapValue * (10 ** toDecimals)) / toPrice
425
+ : 0
426
+ const rebaseFeeOutRate = await this.rebaseFeeRate(toToken, false, estimatedToAmount)
427
+ const rebaseFeeOutValue = swapValue * rebaseFeeOutRate
428
+
429
+ const swapImpactCfg = await this.#getSwapImpactConfig()
430
+ const swapImpactFeeValue = swapImpactCfg?.enabled
431
+ ? USDZDataAPI.#computeSwapImpactFeeValue(swapValue, totalVaultsValue, swapImpactCfg.impactMultiplier, swapImpactCfg.maxImpactRate)
432
+ : 0
433
+
434
+ const emaCfg = await this.#getEmaVolatilityFeeConfig()
435
+ const emaVolatilityFeeValue = emaCfg?.enabled
436
+ ? USDZDataAPI.#computeEmaVolatilityFeeValue(
437
+ swapValue,
438
+ USDZDataAPI.#maxEmaDivergenceRate(fromFeed, toFeed),
439
+ emaCfg.multiplier,
440
+ emaCfg.maxFeeRate,
441
+ )
442
+ : 0
443
+
444
+ const totalFeeValue = rebaseFeeInValue + rebaseFeeOutValue + swapImpactFeeValue + emaVolatilityFeeValue
445
+ const totalFeeRate = swapValue !== 0 ? totalFeeValue / swapValue : 0
446
+
447
+ return {
448
+ swapValue,
449
+ totalVaultsValue,
450
+ rebaseFeeInRate,
451
+ rebaseFeeOutRate,
452
+ rebaseFeeInValue,
453
+ rebaseFeeOutValue,
454
+ swapImpactFeeValue,
455
+ emaVolatilityFeeValue,
456
+ totalFeeValue,
457
+ totalFeeRate,
458
+ }
459
+ }
460
+
461
+ async #getTotalVaultsValueUsd(timestamp: number): Promise<number> {
462
+ const vaultKeys = Object.keys(this.consts.zoCore.vaults)
463
+ const vaultValues = await Promise.all(vaultKeys.map(async (vault) => {
464
+ const vaultInfo = await this.getVaultInfo(vault)
465
+ const reservingFeeDelta = USDZDataAPI.calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, timestamp)
466
+ const totalVaultAmount = reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount
467
+ const oraclePrice = (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked()
468
+ const { decimals } = this.consts.coins[vault]
469
+ return totalVaultAmount * oraclePrice / (10 ** decimals)
470
+ }))
471
+ return vaultValues.reduce((acc, curr) => acc + curr, 0)
472
+ }
473
+
474
+ async #getSwapImpactConfig(): Promise<SwapImpactConfig | null> {
475
+ const raw = await this.#getMarketDynamicFieldObjectByKeySuffix(this.consts.zoCore.market, 'SwapImpactConfigKey')
476
+ if (!raw)
477
+ return null
478
+ return USDZDataAPI.#parseSwapImpactConfig(raw)
479
+ }
480
+
481
+ async #getEmaVolatilityFeeConfig(): Promise<EmaVolatilityFeeConfig | null> {
482
+ const raw = await this.#getMarketDynamicFieldObjectByKeySuffix(this.consts.zoCore.market, 'EmaVolatilityFeeConfigKey')
483
+ if (!raw)
484
+ return null
485
+ return USDZDataAPI.#parseEmaVolatilityFeeConfig(raw)
486
+ }
487
+
488
+ async #getMarketDynamicFieldObjectByKeySuffix(parentId: string, keyTypeSuffix: string): Promise<any | null> {
489
+ let cursor: string | null | undefined
490
+ let hasNextPage = true
491
+
492
+ while (hasNextPage) {
493
+ const page = await this.provider.getDynamicFields({ parentId, cursor })
494
+ for (const field of page.data) {
495
+ const type = (field.name as any)?.type
496
+ if (typeof type === 'string' && type.endsWith(`::${keyTypeSuffix}`)) {
497
+ return await this.provider.getDynamicFieldObject({
498
+ parentId,
499
+ name: field.name as any,
500
+ })
501
+ }
502
+ }
503
+ hasNextPage = page.hasNextPage
504
+ cursor = page.nextCursor
505
+ }
506
+ return null
507
+ }
508
+
509
+ static #parseSwapImpactConfig(raw: any): SwapImpactConfig {
510
+ const { fields } = raw.data.content
511
+ return {
512
+ id: fields.id.id,
513
+ enabled: fields.enabled,
514
+ impactMultiplier: parseValue(fields.impact_multiplier),
515
+ maxImpactRate: parseValue(fields.max_impact_rate),
516
+ }
517
+ }
518
+
519
+ static #parseEmaVolatilityFeeConfig(raw: any): EmaVolatilityFeeConfig {
520
+ const { fields } = raw.data.content
521
+ return {
522
+ id: fields.id.id,
523
+ enabled: fields.enabled,
524
+ multiplier: parseValue(fields.multiplier),
525
+ maxFeeRate: parseValue(fields.max_fee_rate),
526
+ }
527
+ }
528
+
529
+ static #computeSwapImpactFeeValue(swapValue: number, totalVaultsValue: number, impactMultiplier: number, maxImpactRate: number): number {
530
+ if (swapValue <= 0 || totalVaultsValue <= 0)
531
+ return 0
532
+ const utilization = swapValue / totalVaultsValue
533
+ const rawImpactRate = impactMultiplier * utilization
534
+ const impactRate = Math.min(rawImpactRate, maxImpactRate)
535
+ return swapValue * impactRate
536
+ }
537
+
538
+ static #computeEmaVolatilityFeeValue(swapValue: number, maxDiv: number, multiplier: number, maxFeeRate: number): number {
539
+ if (swapValue <= 0 || maxDiv <= 0)
540
+ return 0
541
+ const rawFeeRate = multiplier * maxDiv
542
+ const feeRate = Math.min(rawFeeRate, maxFeeRate)
543
+ return swapValue * feeRate
544
+ }
545
+
546
+ static #maxEmaDivergenceRate(sourceFeed: any, destFeed: any): number {
547
+ const sourceDiv = USDZDataAPI.#emaDivergenceRate(sourceFeed)
548
+ const destDiv = USDZDataAPI.#emaDivergenceRate(destFeed)
549
+ return Math.max(sourceDiv, destDiv)
550
+ }
551
+
552
+ static #emaDivergenceRate(priceFeed: any): number {
553
+ const price = priceFeed.getPriceUnchecked().getPriceAsNumberUnchecked()
554
+ const ema = priceFeed.getEmaPriceUnchecked().getPriceAsNumberUnchecked()
555
+ const denom = Math.abs(price)
556
+ if (denom === 0)
557
+ return 0
558
+ return Math.abs(price - ema) / denom
559
+ }
560
+
381
561
  public async getPositionCapInfoList(owner: string): Promise<IUSDZPositionCapInfo[]> {
382
562
  const positionCapInfoList: IUSDZPositionCapInfo[] = []
383
563
  let cursor: string | undefined | null
@@ -631,7 +811,7 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
631
811
  const longSize = longSymbol.openingSize
632
812
  const shortSize = shortSymbol.openingSize
633
813
 
634
- const deltaRate = USDZDataAPI.calcOiFundingFeeRate(oiState.model, longSize, shortSize, elapsed)
814
+ const deltaRate = USDZDataAPI.calcOiFundingFeeRate(oiState.model, longSize, shortSize, elapsed, oiState.maxOiLong, oiState.maxOiShort)
635
815
  return long ? deltaRate : -deltaRate
636
816
  }
637
817
 
@@ -718,12 +898,42 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
718
898
  return pnlPerRate >= 0 ? -secondsRate : secondsRate
719
899
  }
720
900
 
721
- private static calcOiFundingFeeRate(model: IUSDZOiFundingModel, longSize: number, shortSize: number, elapsed: number): number {
722
- const imbalance = Math.abs(longSize - shortSize)
723
- const total = longSize + shortSize > 0 ? longSize + shortSize : 1
724
- const dailyRate = Math.min((model.multiplier * (imbalance ** model.exponent)) / total, model.max)
725
- const secondsRate = dailyRate * elapsed / SECONDS_PER_EIGHT_HOUR
726
- return longSize >= shortSize ? secondsRate : -secondsRate
901
+ /**
902
+ * OI funding rate matching Move compute_oi_funding_rate_capped.
903
+ * When both maxOiLong and maxOiShort are set and > 0, uses normalized skew (oi/cap);
904
+ * otherwise falls back to (long - short) / total.
905
+ */
906
+ private static calcOiFundingFeeRate(
907
+ model: IUSDZOiFundingModel,
908
+ oiLong: number,
909
+ oiShort: number,
910
+ elapsed: number,
911
+ maxOiLong?: number,
912
+ maxOiShort?: number,
913
+ ): number {
914
+ let skew: number
915
+ if (maxOiLong && maxOiShort && maxOiLong > 0 && maxOiShort > 0) {
916
+ const normLong = Math.min(oiLong / maxOiLong, 1)
917
+ const normShort = Math.min(oiShort / maxOiShort, 1)
918
+ skew = normLong - normShort
919
+ }
920
+ else {
921
+ const total = oiLong + oiShort
922
+ if (total === 0)
923
+ return 0
924
+ skew = (oiLong - oiShort) / total
925
+ }
926
+
927
+ if (skew === 0)
928
+ return 0
929
+
930
+ const skewIsPositive = skew > 0
931
+ const skewAbs = Math.abs(skew)
932
+ const exponentInt = Math.floor(model.exponent)
933
+ const skewPow = skewAbs ** exponentInt
934
+ const dailyRate = Math.min(model.multiplier * skewPow, model.max)
935
+ const secondsRate = (dailyRate * elapsed) / SECONDS_PER_EIGHT_HOUR
936
+ return skewIsPositive ? secondsRate : -secondsRate
727
937
  }
728
938
 
729
939
  private static calcAccFundingFeeRate(
@@ -732,16 +942,16 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
732
942
  price: number,
733
943
  lpSupplyAmount: number,
734
944
  timestamp: number,
735
- oiModel?: IUSDZOiFundingModel,
945
+ oiState?: IUSDZOiFundingState,
736
946
  pairedOpeningSize?: number,
737
947
  ): number {
738
948
  if (symbol.lastUpdate > 0) {
739
949
  const elapsed = timestamp - symbol.lastUpdate
740
950
  if (elapsed > 0) {
741
- if (oiModel && typeof pairedOpeningSize === 'number') {
951
+ if (oiState?.enabled && oiState.model && typeof pairedOpeningSize === 'number') {
742
952
  const longSize = symbol.long ? symbol.openingSize : pairedOpeningSize
743
953
  const shortSize = symbol.long ? pairedOpeningSize : symbol.openingSize
744
- const deltaRate = USDZDataAPI.calcOiFundingFeeRate(oiModel, longSize, shortSize, elapsed)
954
+ const deltaRate = USDZDataAPI.calcOiFundingFeeRate(oiState.model, longSize, shortSize, elapsed, oiState.maxOiLong, oiState.maxOiShort)
745
955
  return symbol.accFundingRate + deltaRate
746
956
  }
747
957
  const deltaSize = USDZDataAPI.calcDeltaSize(symbol, price)
@@ -758,7 +968,7 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
758
968
  price: number,
759
969
  lpSupplyAmount: number,
760
970
  timestamp: number,
761
- oiModel?: IUSDZOiFundingModel,
971
+ oiState?: IUSDZOiFundingState,
762
972
  pairedOpeningSize?: number,
763
973
  ): number {
764
974
  const accFundingRate = USDZDataAPI.calcAccFundingFeeRate(
@@ -767,7 +977,7 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
767
977
  price,
768
978
  lpSupplyAmount,
769
979
  timestamp,
770
- oiModel,
980
+ oiState,
771
981
  pairedOpeningSize,
772
982
  )
773
983
  return symbol.unrealisedFundingFeeValue + (accFundingRate - symbol.accFundingRate) * symbol.openingSize
@@ -973,7 +1183,7 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
973
1183
  (await this.getOraclePrice(positionInfo.indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked(),
974
1184
  (await this.getMarketInfo()).lpSupplyWithDecimals,
975
1185
  Date.now() / 1000,
976
- oiState && oiState.enabled ? oiState.model : undefined,
1186
+ oiState && oiState.enabled ? oiState : undefined,
977
1187
  pairedSymbol.openingSize,
978
1188
  )
979
1189
 
@@ -987,7 +1197,7 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
987
1197
  price: number,
988
1198
  lpSupplyAmount: number,
989
1199
  timestamp: number,
990
- oiModel?: IUSDZOiFundingModel,
1200
+ oiState?: IUSDZOiFundingState,
991
1201
  pairedOpeningSize?: number,
992
1202
  ): number {
993
1203
  const accFundingRate = USDZDataAPI.calcAccFundingFeeRate(
@@ -996,7 +1206,7 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
996
1206
  price,
997
1207
  lpSupplyAmount,
998
1208
  timestamp,
999
- oiModel,
1209
+ oiState,
1000
1210
  pairedOpeningSize,
1001
1211
  )
1002
1212
  return position.fundingFeeValue + (accFundingRate - position.lastFundingRate) * position.positionSize
@@ -1038,6 +1248,8 @@ export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
1038
1248
  exponent: parseValue(content.model.fields.exponent),
1039
1249
  max: parseValue(content.model.fields.max),
1040
1250
  },
1251
+ maxOiLong: content.max_oi_long !== null && content.max_oi_long !== undefined ? parseValue(content.max_oi_long) : undefined,
1252
+ maxOiShort: content.max_oi_short !== null && content.max_oi_short !== undefined ? parseValue(content.max_oi_short) : undefined,
1041
1253
  }
1042
1254
  }
1043
1255