{"version":3,"sources":["../../../../src/gfx/cpmm/curve/fee.ts","../../../../src/gfx/cpmm/curve/common.ts"],"sourcesContent":["import BN from \"bn.js\";\nimport { CpmmObservationState, CpmmObservation } from \"../type\";\nimport Decimal from \"decimal.js-light\";\nimport { checkedCeilDiv, saturatingSub } from \"./common\";\n\nexport const ONE_BASIS_POINT = new BN(100);\nexport const FEE_RATE_DENOMINATOR_VALUE = new BN(1_000_000);\nconst OBSERVATION_LEN = 100;\n// Volatility-based fee constants\n// const MAX_FEE_VOLATILITY = new BN(10000); // 1% max fee\nconst VOLATILITY_WINDOW = new BN(3600); // 1 hour window for volatility calculation\n\nconst MAX_FEE = new BN(100000); // 10% max fee\nconst DEFAULT_VOLATILITY_FACTOR = new BN(300000); // Adjust based on desired sensitivity\n\ntype PriceRange = {\n  minPrice: BN;\n  maxPrice: BN;\n  twapPrice: BN;\n};\n\ntype FeeType = \"volatility\";\n\nexport class DynamicFee {\n  static calculateDynamicFee(\n    amount: BN,\n    blockTimestamp: BN,\n    observationState: CpmmObservationState,\n    feeType: FeeType,\n    baseFees: BN,\n    poolVolatilityFactor: BN,\n    isInvokedWithSignedSegmenter: boolean,\n  ): BN {\n    const feeRate = this.calculateDynamicFeeRate(\n      blockTimestamp,\n      observationState,\n      feeType,\n      baseFees,\n      poolVolatilityFactor,\n      isInvokedWithSignedSegmenter,\n    );\n\n    const [dynamicFee, _feeRateDenominator] = checkedCeilDiv(amount.mul(feeRate), FEE_RATE_DENOMINATOR_VALUE);\n    return dynamicFee;\n  }\n\n  static calculatePreDynamicFee(\n    amount: BN,\n    blockTimestamp: BN,\n    observationState: CpmmObservationState,\n    feeType: FeeType,\n    baseFees: BN,\n    poolVolatilityFactor: BN,\n    isInvokedWithSignedSegmenter: boolean,\n  ): BN {\n    const feeRate = this.calculateDynamicFeeRate(\n      blockTimestamp,\n      observationState,\n      feeType,\n      baseFees,\n      poolVolatilityFactor,\n      isInvokedWithSignedSegmenter,\n    );\n\n    if (feeRate.isZero()) {\n      return amount; // No fee, pre-fee amount = post-fee amount\n    }\n\n    const denominator = FEE_RATE_DENOMINATOR_VALUE.sub(feeRate);\n    if (denominator.isZero()) {\n      throw new Error(\"Fee rate equals denominator, causing division by zero\");\n    }\n\n    // x = (y * D + (D - r) - 1) / (D - r)\n    const numerator = amount.mul(FEE_RATE_DENOMINATOR_VALUE);\n    const result = numerator.add(denominator).sub(new BN(1)).div(denominator);\n\n    return result;\n  }\n\n  static calculateDynamicFeeRate(\n    blockTimestamp: BN,\n    observationState: CpmmObservationState,\n    feeType: FeeType,\n    baseFees: BN,\n    poolVolatilityFactor: BN,\n    isInvokedWithSignedSegmenter: boolean,\n  ): BN {\n    switch (feeType) {\n      case \"volatility\": {\n        return this.calculateVolatileFee(\n          blockTimestamp,\n          observationState,\n          baseFees,\n          poolVolatilityFactor,\n          isInvokedWithSignedSegmenter,\n        );\n      }\n    }\n  }\n\n  static calculateVolatileFee(\n    blockTimestamp: BN,\n    observationState: CpmmObservationState,\n    baseFees: BN,\n    poolSpecifiedVolatilityFactor: BN,\n    isInvokedWithSignedSegmenter: boolean,\n  ): BN {\n    const { minPrice, maxPrice, twapPrice } = this.getPriceRange(observationState, blockTimestamp, VOLATILITY_WINDOW);\n    if (minPrice.eqn(0) || maxPrice.eqn(0) || twapPrice.eqn(0) || twapPrice.eqn(1)) {\n      return baseFees;\n    }\n\n    const logMaxPrice = new Decimal(maxPrice.toString()).ln();\n    const logMinPrice = new Decimal(minPrice.toString()).ln();\n    const logTwapPrice = new Decimal(twapPrice.toString()).ln();\n\n    const numerator = logMaxPrice.sub(logMinPrice);\n    const denominator = logTwapPrice.abs();\n\n    if (denominator.eq(0)) {\n      return baseFees;\n    }\n\n    const volatility = numerator.div(denominator);\n    const volatilityFactor = poolSpecifiedVolatilityFactor.eqn(0)\n      ? DEFAULT_VOLATILITY_FACTOR\n      : poolSpecifiedVolatilityFactor;\n    const volatilityComponent = new Decimal(volatilityFactor.toString()).mul(volatility);\n\n    const dynamicFee = new Decimal(baseFees.toString()).add(volatilityComponent);\n    const finalFee = new BN(\n      dynamicFee.lessThan(new Decimal(MAX_FEE.toString())) ? dynamicFee.toString() : MAX_FEE.toString(),\n    );\n    if (isInvokedWithSignedSegmenter) {\n      const discountedFee = saturatingSub(finalFee, ONE_BASIS_POINT);\n      return baseFees.gt(discountedFee) ? baseFees : discountedFee;\n    } else {\n      return finalFee;\n    }\n  }\n\n  static getPriceRange(observationState: CpmmObservationState, currentTime: BN, window: BN): PriceRange {\n    let minPrice = new BN(1).ushln(128).subn(1);\n    let maxPrice = new BN(0);\n\n    let descendingObservations = observationState.observations\n      .map((observation, idx) => ({ observation, idx }))\n      .filter(({ observation }) => {\n        observation.blockTimestamp.eqn(0) &&\n          !observation.cumulativeToken0PriceX32.eqn(0) &&\n          !observation.cumulativeToken1PriceX32.eqn(0) &&\n          currentTime.sub(observation.blockTimestamp) <= window;\n      })\n      .map(({ observation, idx }) => {\n        return {\n          index: idx,\n          observation,\n        };\n      });\n\n    if (descendingObservations.length < 2) {\n      return {\n        minPrice: new BN(0),\n        maxPrice: new BN(0),\n        twapPrice: new BN(0),\n      };\n    }\n\n    descendingObservations.sort((a, b) => b.observation.blockTimestamp.cmp(a.observation.blockTimestamp));\n\n    const newestObs = descendingObservations[0];\n    const oldestObs = descendingObservations[descendingObservations.length - 1];\n\n    const totalTimeDelta = saturatingSub(newestObs.observation.blockTimestamp, oldestObs.observation.blockTimestamp);\n    if (totalTimeDelta.eqn(0)) {\n      return {\n        minPrice: new BN(0),\n        maxPrice: new BN(0),\n        twapPrice: new BN(0),\n      };\n    }\n\n    const twapPrice = newestObs.observation.cumulativeToken0PriceX32\n      .sub(oldestObs.observation.cumulativeToken0PriceX32)\n      .div(totalTimeDelta);\n\n    for (const indexedObservation of descendingObservations) {\n      let lastObservation: CpmmObservation;\n      if (indexedObservation.index == 0) {\n        lastObservation = observationState.observations[OBSERVATION_LEN - 1];\n      } else {\n        lastObservation = observationState.observations[indexedObservation.index - 1];\n      }\n\n      if (lastObservation.blockTimestamp.eqn(0)) {\n        continue;\n      }\n\n      if (lastObservation.blockTimestamp > indexedObservation.observation.blockTimestamp) {\n        break;\n      }\n\n      const nextObservation = indexedObservation.observation;\n      const timeDelta = saturatingSub(nextObservation.blockTimestamp, lastObservation.blockTimestamp);\n\n      if (timeDelta.eqn(0)) {\n        continue;\n      }\n\n      const price = nextObservation.cumulativeToken0PriceX32\n        .sub(lastObservation.cumulativeToken0PriceX32)\n        .div(timeDelta);\n\n      minPrice = BN.min(minPrice, price);\n      maxPrice = BN.max(maxPrice, price);\n    }\n\n    return {\n      minPrice,\n      maxPrice,\n      twapPrice,\n    };\n  }\n\n  static calculatePreFeeAmount(\n    blockTimestamp: BN,\n    postFeeAmount: BN,\n    observationState: CpmmObservationState,\n    feeType: FeeType,\n    baseFees: BN,\n    poolVolatilityFactor: BN,\n    isInvokedWithSignedSegmenter: boolean,\n  ): BN {\n    const dynamicFeeRate = this.calculateDynamicFeeRate(\n      blockTimestamp,\n      observationState,\n      feeType,\n      baseFees,\n      poolVolatilityFactor,\n      isInvokedWithSignedSegmenter,\n    );\n    if (dynamicFeeRate.eqn(0)) {\n      return postFeeAmount;\n    } else {\n      const numerator = postFeeAmount.mul(FEE_RATE_DENOMINATOR_VALUE);\n      const denominator = FEE_RATE_DENOMINATOR_VALUE.sub(dynamicFeeRate);\n\n      return numerator.add(denominator).subn(1).div(denominator);\n    }\n  }\n}\n","import BN from \"bn.js\";\n\nexport const ZERO = new BN(0);\n\nexport function checkedRem(dividend: BN, divisor: BN): BN {\n  if (divisor.isZero()) throw Error(\"divisor is zero\");\n\n  const result = dividend.mod(divisor);\n  return result;\n}\n\nexport function checkedCeilDiv(dividend: BN, rhs: BN): BN[] {\n  if (rhs.isZero()) throw Error(\"rhs is zero\");\n  const quotient = dividend.div(rhs);\n  if (quotient.isZero()) return [quotient, rhs];\n  const remainder = dividend.sub(quotient.mul(rhs));\n  if (remainder.isZero()) return [quotient, rhs];\n  return [quotient.add(new BN(1)), rhs];\n}\n\nexport function saturatingSub(a: BN, b: BN): BN {\n  return a.gt(b) ? a.sub(b) : new BN(0);\n}\n"],"mappings":"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","names":["BN","Decimal","BN","ZERO","checkedCeilDiv","dividend","rhs","quotient","BN","saturatingSub","a","b","ONE_BASIS_POINT","BN","FEE_RATE_DENOMINATOR_VALUE","OBSERVATION_LEN","VOLATILITY_WINDOW","MAX_FEE","DEFAULT_VOLATILITY_FACTOR","DynamicFee","amount","blockTimestamp","observationState","feeType","baseFees","poolVolatilityFactor","isInvokedWithSignedSegmenter","feeRate","dynamicFee","_feeRateDenominator","checkedCeilDiv","denominator","poolSpecifiedVolatilityFactor","minPrice","maxPrice","twapPrice","logMaxPrice","Decimal","logMinPrice","logTwapPrice","numerator","volatility","volatilityFactor","volatilityComponent","finalFee","discountedFee","saturatingSub","currentTime","window","descendingObservations","observation","idx","a","b","newestObs","oldestObs","totalTimeDelta","indexedObservation","lastObservation","nextObservation","timeDelta","price","postFeeAmount","dynamicFeeRate"]}