{"version":3,"sources":["../../../../src/gfx/cpmm/curve/constantProduct.ts","../../../../src/gfx/cpmm/curve/calculator.ts","../../../../src/gfx/cpmm/curve/fee.ts","../../../../src/gfx/cpmm/curve/common.ts"],"sourcesContent":["import BN from \"bn.js\";\nimport { RoundDirection, SwapWithoutFeesResult, TradingTokenResult } from \"./calculator\";\nimport { checkedCeilDiv, checkedRem, ZERO } from \"./common\";\n\nexport class ConstantProductCurve {\n  static swapWithoutFees(sourceAmount: BN, swapSourceAmount: BN, swapDestinationAmount: BN): SwapWithoutFeesResult {\n    const invariant = swapSourceAmount.mul(swapDestinationAmount);\n\n    const newSwapSourceAmount = swapSourceAmount.add(sourceAmount);\n    const [newSwapDestinationAmount, _newSwapSourceAmount] = checkedCeilDiv(invariant, newSwapSourceAmount);\n\n    const sourceAmountSwapped = _newSwapSourceAmount.sub(swapSourceAmount);\n    const destinationAmountSwapped = swapDestinationAmount.sub(newSwapDestinationAmount);\n    if (destinationAmountSwapped.isZero()) throw Error(\"destinationAmountSwapped is zero\");\n\n    return {\n      sourceAmountSwapped,\n      destinationAmountSwapped,\n    };\n  }\n\n  static swapWithoutFeesBaseOut(\n    destinationAmount: BN,\n    swapSourceAmount: BN,\n    swapDestinationAmount: BN,\n  ): SwapWithoutFeesResult {\n    // Ensure inputs are valid\n    if (destinationAmount.isZero()) {\n      throw new Error(\"destinationAmount is zero\");\n    }\n    if (destinationAmount.gt(swapDestinationAmount)) {\n      throw new Error(\"destinationAmount exceeds available destination reserve\");\n    }\n\n    // Numerator: x * Δy\n    const numerator = swapSourceAmount.mul(destinationAmount);\n    // Denominator: y - Δy\n    const denominator = swapDestinationAmount.sub(destinationAmount);\n\n    if (denominator.isZero()) {\n      throw new Error(\"denominator is zero\");\n    }\n\n    // Ceiling division: Δx = ceil((x * Δy) / (y - Δy))\n    const [sourceAmountSwapped] = checkedCeilDiv(numerator, denominator);\n\n    return {\n      sourceAmountSwapped,\n      destinationAmountSwapped: destinationAmount,\n    };\n  }\n\n  static lpTokensToTradingTokens(\n    lpTokenAmount: BN,\n    lpTokenSupply: BN,\n    swapTokenAmount0: BN,\n    swapTokenAmount1: BN,\n    roundDirection: RoundDirection,\n  ): TradingTokenResult {\n    let tokenAmount0 = lpTokenAmount.mul(swapTokenAmount0).div(lpTokenSupply);\n    let tokenAmount1 = lpTokenAmount.mul(swapTokenAmount1).div(lpTokenSupply);\n\n    if (roundDirection === RoundDirection.Floor) {\n      return { tokenAmount0, tokenAmount1 };\n    } else if (roundDirection === RoundDirection.Ceiling) {\n      const tokenRemainder0 = checkedRem(lpTokenAmount.mul(swapTokenAmount0), lpTokenSupply);\n\n      if (tokenRemainder0.gt(ZERO) && tokenAmount0.gt(ZERO)) {\n        tokenAmount0 = tokenAmount0.add(new BN(1));\n      }\n\n      const token1Remainder = checkedRem(lpTokenAmount.mul(swapTokenAmount1), lpTokenSupply);\n\n      if (token1Remainder.gt(ZERO) && tokenAmount1.gt(ZERO)) {\n        tokenAmount1 = tokenAmount1.add(new BN(1));\n      }\n\n      return { tokenAmount0, tokenAmount1 };\n    }\n    throw Error(\"roundDirection value error\");\n  }\n}\n","import BN from \"bn.js\";\nimport { DynamicFee } from \"./fee\";\nimport { ConstantProductCurve } from \"./constantProduct\";\nimport { CpmmObservationState } from \"../type\";\n\nexport enum RoundDirection {\n  Floor,\n  Ceiling,\n}\n\nexport type SwapWithoutFeesResult = { sourceAmountSwapped: BN; destinationAmountSwapped: BN };\n\nexport type TradingTokenResult = { tokenAmount0: BN; tokenAmount1: BN };\n\nexport type SwapResult = {\n  newSwapSourceAmount: BN;\n  newSwapDestinationAmount: BN;\n  sourceAmountSwapped: BN;\n  destinationAmountSwapped: BN;\n  tradeFee: BN;\n};\n\nexport class CurveCalculator {\n  static validate_supply(tokenAmount0: BN, tokenAmount1: BN): void {\n    if (tokenAmount0.isZero()) throw Error(\"tokenAmount0 is zero\");\n    if (tokenAmount1.isZero()) throw Error(\"tokenAmount1 is zero\");\n  }\n\n  static swapBaseIn(\n    sourceAmount: BN,\n    swapSourceAmount: BN,\n    swapDestinationAmount: BN,\n    tradeFeeRate: BN,\n    observationState: CpmmObservationState,\n    poolVolatilityFactor: BN,\n    isInvokedWithSignedSegmenter = false,\n  ): SwapResult {\n    const tradeFee = DynamicFee.calculateDynamicFee(\n      sourceAmount,\n      new BN(new Date().getTime() / 1000),\n      observationState,\n      \"volatility\",\n      tradeFeeRate,\n      poolVolatilityFactor,\n      isInvokedWithSignedSegmenter,\n    );\n\n    const sourceAmountLessFees = sourceAmount.sub(tradeFee);\n\n    const { sourceAmountSwapped, destinationAmountSwapped } = ConstantProductCurve.swapWithoutFees(\n      sourceAmountLessFees,\n      swapSourceAmount,\n      swapDestinationAmount,\n    );\n\n    const _sourceAmountSwapped = sourceAmountSwapped.add(tradeFee);\n    return {\n      newSwapSourceAmount: swapSourceAmount.add(_sourceAmountSwapped),\n      newSwapDestinationAmount: swapDestinationAmount.sub(destinationAmountSwapped),\n      sourceAmountSwapped: _sourceAmountSwapped,\n      destinationAmountSwapped,\n      tradeFee,\n    };\n  }\n\n  static swapBaseOut(\n    destinationAmount: BN,\n    swapSourceAmount: BN,\n    swapDestinationAmount: BN,\n    tradeFeeRate: BN,\n    observationState: CpmmObservationState,\n    poolVolatilityFactor: BN,\n    isInvokedWithSignedSegmenter = false,\n  ): SwapResult {\n    // Validate inputs\n    if (destinationAmount.isZero()) throw new Error(\"destinationAmount is zero\");\n    if (destinationAmount.gt(swapDestinationAmount)) {\n      throw new Error(\"destinationAmount exceeds available destination reserve\");\n    }\n\n    // Calculate source amount without fees\n    const { sourceAmountSwapped: sourceAmountLessFees, destinationAmountSwapped } =\n      ConstantProductCurve.swapWithoutFeesBaseOut(destinationAmount, swapSourceAmount, swapDestinationAmount);\n\n    const sourceAmount = DynamicFee.calculatePreDynamicFee(\n      sourceAmountLessFees,\n      new BN(new Date().getTime() / 1000),\n      observationState,\n      \"volatility\",\n      tradeFeeRate,\n      poolVolatilityFactor,\n      isInvokedWithSignedSegmenter,\n    );\n\n    return {\n      newSwapSourceAmount: swapSourceAmount.add(sourceAmount),\n      newSwapDestinationAmount: swapDestinationAmount.sub(destinationAmountSwapped),\n      sourceAmountSwapped: sourceAmount,\n      destinationAmountSwapped,\n      tradeFee: sourceAmount.sub(sourceAmountLessFees),\n    };\n  }\n}\n","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":"AAAA,OAAOA,MAAQ,QCAf,OAAOC,MAAQ,QCAf,OAAOC,MAAQ,QAEf,OAAOC,MAAa,mBCFpB,OAAOC,MAAQ,QAER,IAAMC,EAAO,IAAID,EAAG,CAAC,EAErB,SAASE,EAAWC,EAAcC,EAAiB,CACxD,GAAIA,EAAQ,OAAO,EAAG,MAAM,MAAM,iBAAiB,EAGnD,OADeD,EAAS,IAAIC,CAAO,CAErC,CAEO,SAASC,EAAeF,EAAcG,EAAe,CAC1D,GAAIA,EAAI,OAAO,EAAG,MAAM,MAAM,aAAa,EAC3C,IAAMC,EAAWJ,EAAS,IAAIG,CAAG,EACjC,OAAIC,EAAS,OAAO,EAAU,CAACA,EAAUD,CAAG,EAC1BH,EAAS,IAAII,EAAS,IAAID,CAAG,CAAC,EAClC,OAAO,EAAU,CAACC,EAAUD,CAAG,EACtC,CAACC,EAAS,IAAI,IAAIP,EAAG,CAAC,CAAC,EAAGM,CAAG,CACtC,CDbO,IAAME,EAAkB,IAAIC,EAAG,GAAG,EAC5BC,EAA6B,IAAID,EAAG,GAAS,EAI1D,IAAME,EAAoB,IAAIC,EAAG,IAAI,EAE/BC,EAAU,IAAID,EAAG,GAAM,EACvBE,EAA4B,IAAIF,EAAG,GAAM,EFTxC,IAAMG,EAAN,KAA2B,CAChC,OAAO,gBAAgBC,EAAkBC,EAAsBC,EAAkD,CAC/G,IAAMC,EAAYF,EAAiB,IAAIC,CAAqB,EAEtDE,EAAsBH,EAAiB,IAAID,CAAY,EACvD,CAACK,EAA0BC,CAAoB,EAAIC,EAAeJ,EAAWC,CAAmB,EAEhGI,EAAsBF,EAAqB,IAAIL,CAAgB,EAC/DQ,EAA2BP,EAAsB,IAAIG,CAAwB,EACnF,GAAII,EAAyB,OAAO,EAAG,MAAM,MAAM,kCAAkC,EAErF,MAAO,CACL,oBAAAD,EACA,yBAAAC,CACF,CACF,CAEA,OAAO,uBACLC,EACAT,EACAC,EACuB,CAEvB,GAAIQ,EAAkB,OAAO,EAC3B,MAAM,IAAI,MAAM,2BAA2B,EAE7C,GAAIA,EAAkB,GAAGR,CAAqB,EAC5C,MAAM,IAAI,MAAM,yDAAyD,EAI3E,IAAMS,EAAYV,EAAiB,IAAIS,CAAiB,EAElDE,EAAcV,EAAsB,IAAIQ,CAAiB,EAE/D,GAAIE,EAAY,OAAO,EACrB,MAAM,IAAI,MAAM,qBAAqB,EAIvC,GAAM,CAACJ,CAAmB,EAAID,EAAeI,EAAWC,CAAW,EAEnE,MAAO,CACL,oBAAAJ,EACA,yBAA0BE,CAC5B,CACF,CAEA,OAAO,wBACLG,EACAC,EACAC,EACAC,EACAC,EACoB,CACpB,IAAIC,EAAeL,EAAc,IAAIE,CAAgB,EAAE,IAAID,CAAa,EACpEK,EAAeN,EAAc,IAAIG,CAAgB,EAAE,IAAIF,CAAa,EAExE,GAAIG,IAAmB,EACrB,MAAO,CAAE,aAAAC,EAAc,aAAAC,CAAa,EAC/B,GAAIF,IAAmB,EAG5B,OAFwBG,EAAWP,EAAc,IAAIE,CAAgB,EAAGD,CAAa,EAEjE,GAAGO,CAAI,GAAKH,EAAa,GAAGG,CAAI,IAClDH,EAAeA,EAAa,IAAI,IAAII,EAAG,CAAC,CAAC,GAGnBF,EAAWP,EAAc,IAAIG,CAAgB,EAAGF,CAAa,EAEjE,GAAGO,CAAI,GAAKF,EAAa,GAAGE,CAAI,IAClDF,EAAeA,EAAa,IAAI,IAAIG,EAAG,CAAC,CAAC,GAGpC,CAAE,aAAAJ,EAAc,aAAAC,CAAa,EAEtC,MAAM,MAAM,4BAA4B,CAC1C,CACF","names":["BN","BN","BN","Decimal","BN","ZERO","checkedRem","dividend","divisor","checkedCeilDiv","rhs","quotient","ONE_BASIS_POINT","BN","FEE_RATE_DENOMINATOR_VALUE","VOLATILITY_WINDOW","BN","MAX_FEE","DEFAULT_VOLATILITY_FACTOR","ConstantProductCurve","sourceAmount","swapSourceAmount","swapDestinationAmount","invariant","newSwapSourceAmount","newSwapDestinationAmount","_newSwapSourceAmount","checkedCeilDiv","sourceAmountSwapped","destinationAmountSwapped","destinationAmount","numerator","denominator","lpTokenAmount","lpTokenSupply","swapTokenAmount0","swapTokenAmount1","roundDirection","tokenAmount0","tokenAmount1","checkedRem","ZERO","BN"]}