import BN from 'bn.js' import { Decimal } from 'decimal.js' import type { CoinAmounts } from '../type/clmm' /** * Percentage - the util set for percentage struct. */ export class Percentage { readonly numerator: BN readonly denominator: BN constructor(numerator: BN, denominator: BN) { this.toString = () => { return `${this.numerator.toString()}/${this.denominator.toString()}` } this.numerator = numerator this.denominator = denominator } /** * Get the percentage of a number. * * @param number * @returns */ static fromDecimal(number: Decimal): Percentage { return Percentage.fromFraction(number.toDecimalPlaces(1).mul(10).toNumber(), 1000) } /** * Convert the percentage to a Decimal * * @returns Decimal representation of the percentage */ toDecimal(): Decimal { return new Decimal(this.numerator.toString()).div(this.denominator.toString()).mul(100) } /** * Get the percentage of a fraction. * * @param numerator * @param denominator * @returns */ static fromFraction(numerator: BN | number, denominator: BN | number): Percentage { const num = typeof numerator === 'number' ? new BN(numerator.toString()) : numerator const denom = typeof denominator === 'number' ? new BN(denominator.toString()) : denominator return new Percentage(num, denom) } } export function adjustForSlippage(n: BN, { numerator, denominator }: Percentage, adjustUp: boolean): BN { if (adjustUp) { return n.mul(denominator.add(numerator)).div(denominator) } return n.mul(denominator).div(denominator.add(numerator)) } /** * Adjusts token amounts based on slippage tolerance * @param tokenAmount - The input token amounts * @param slippage - The slippage percentage * @param adjustUp - If true, adjusts up for maximum amount, if false adjusts down for minimum amount * @throws Error if token amounts are invalid * @returns Object with adjusted coin limits */ export function adjustForCoinSlippage( tokenAmount: CoinAmounts, slippage: Percentage, adjustUp: boolean ): { coin_amount_limit_a: string; coin_amount_limit_b: string } { if (!tokenAmount?.coin_amount_a || !tokenAmount?.coin_amount_b) { throw new Error('Invalid token amounts') } try { const coinLimitA = adjustForSlippage(new BN(tokenAmount.coin_amount_a), slippage, adjustUp) const coinLimitB = adjustForSlippage(new BN(tokenAmount.coin_amount_b), slippage, adjustUp) return { coin_amount_limit_a: coinLimitA.toString(), coin_amount_limit_b: coinLimitB.toString(), } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : 'Unknown error' throw new Error(`Failed to adjust for slippage: ${errorMessage}`) } }