import { AccountAddress, InputEntryFunctionData, Serializer, U256, } from "@aptos-labs/ts-sdk"; import { AddAMMLiquidityParams, AddCLMMLiquidityParams, AddStableLiquidityParams, CollectFeeParams, CreateAMMPoolAndAddLiquidityParams, CreateCLMMPoolAndAddLiquidityParams, CreateStablePoolAndAddLiquidityParams, RemoveMultipleAMMLiquidityParams, RemoveMultipleCLMMLiquidityParams, RemoveMultipleStableLiquidityParams, RemoveSingleAMMLiquidityParams, RemoveSingleCLMMLiquidityParams, RemoveSingleStableLiquidityParams, } from "../types/position.type"; import { TappSDK } from "../core"; import { getSpacingFromFee, priceToTickIndex, roundPriceToTickSpacing, toU64, } from "../helpers"; const TICK_BOUND = 443636; const BASE = 1.0001; const EMPTY_MIN_PRICE = Math.pow(BASE, -TICK_BOUND); export default class PositionModule { protected _sdk: TappSDK; constructor(sdk: TappSDK) { this._sdk = sdk; } removeSingleAMMLiquidity( params: RemoveSingleAMMLiquidityParams ): InputEntryFunctionData { const serializer = new Serializer(); serializer.serializeStr(params.poolId); serializer.serializeU64(1); serializer.serialize(AccountAddress.fromString(params.positionAddr)); serializer.serializeU128(params.mintedShare); serializer.serializeU64(params.minAmount0); serializer.serializeU64(params.minAmount1); return { function: `${this._sdk.sdkConfig.contractAddress}::router::remove_liquidity`, functionArguments: [serializer.toUint8Array()], }; } removeMultipleAMMLiquidity( params: RemoveMultipleAMMLiquidityParams ): InputEntryFunctionData { const serializer = new Serializer(); serializer.serializeStr(params.poolId); serializer.serializeU64(params.positions.length); params.positions.forEach((position) => { serializer.serialize(AccountAddress.fromString(position.positionAddr)); serializer.serializeU128(position.mintedShare); serializer.serializeU64(position.minAmount0); serializer.serializeU64(position.minAmount1); }); return { function: `${this._sdk.sdkConfig.contractAddress}::router::remove_multi_liquidity`, functionArguments: [serializer.toUint8Array()], }; } removeSingleCLMMLiquidity( params: RemoveSingleCLMMLiquidityParams ): InputEntryFunctionData { const serializer = new Serializer(); serializer.serializeStr(params.poolId); serializer.serialize(AccountAddress.fromString(params.positionAddr)); serializer.serializeU128(params.mintedShare); serializer.serializeU64(params.minAmount0); serializer.serializeU64(params.minAmount1); return { function: `${this._sdk.sdkConfig.contractAddress}::router::remove_liquidity`, functionArguments: [serializer.toUint8Array()], }; } removeMultipleCLMMLiquidity( params: RemoveMultipleCLMMLiquidityParams ): InputEntryFunctionData { const serializer = new Serializer(); serializer.serializeStr(params.poolId); serializer.serializeU64(params.positions.length); params.positions.forEach((position) => { serializer.serialize(AccountAddress.fromString(position.positionAddr)); serializer.serializeU128(position.mintedShare); serializer.serializeU64(position.minAmount0); serializer.serializeU64(position.minAmount1); }); return { function: `${this._sdk.sdkConfig.contractAddress}::router::remove_multi_liquidity`, functionArguments: [serializer.toUint8Array()], }; } removeSingleStableLiquidity( params: RemoveSingleStableLiquidityParams ): InputEntryFunctionData { const serializer = new Serializer(); serializer.serializeStr(params.poolId); serializer.serializeU64(1); serializer.serialize( AccountAddress.fromString(params.position.positionAddr) ); serializer.serializeU8(params.liquidityType); switch (params.liquidityType) { case 0: { // not implemented serializer.serializeU256(0); // burn_amount serializer.serializeU64(0); // i serializer.serializeU256(0); // min received break; } case 1: { // not implemented serializer.serializeVector([new U256(0)]); // amounts serializer.serializeU256(0); // burn amount break; } default: { const amounts = params.position.amounts.map((amount) => { return new U256(amount); }); serializer.serializeU256(params.position.mintedShare); serializer.serializeVector(amounts); // min amounts break; } } return { function: `${this._sdk.sdkConfig.contractAddress}::router::remove_liquidity`, functionArguments: [serializer.toUint8Array()], }; } removeMultipleStableLiquidity( params: RemoveMultipleStableLiquidityParams ): InputEntryFunctionData { const serializer = new Serializer(); serializer.serializeStr(params.poolId); serializer.serializeU64(params.positions.length); params.positions.forEach((position) => { serializer.serialize(AccountAddress.fromString(position.positionAddr)); serializer.serializeU8(params.liquidityType); switch (params.liquidityType) { case 0: { // not implemented serializer.serializeU256(0); // burn_amount serializer.serializeU64(0); // i serializer.serializeU256(0); // min received break; } case 1: { // not implemented serializer.serializeVector([new U256(0)]); // amounts serializer.serializeU256(0); // burn amount break; } default: { const amounts = position.amounts.map((amount) => { return new U256(amount); }); serializer.serializeU256(position.mintedShare); serializer.serializeVector(amounts); // min amounts break; } } }); return { function: `${this._sdk.sdkConfig.contractAddress}::router::remove_multi_liquidity`, functionArguments: [serializer.toUint8Array()], }; } addAMMLiquidity(params: AddAMMLiquidityParams): InputEntryFunctionData { const ser = new Serializer(); ser.serializeStr(params.poolId); ser.serializeOption(); ser.serializeU64(params.amountA); ser.serializeU64(params.amountB); ser.serializeU64(0); ser.serializeU64(0); return { function: `${this._sdk.sdkConfig.contractAddress}::router::add_liquidity`, functionArguments: [ser.toUint8Array()], }; } addCLMMLiquidity(params: AddCLMMLiquidityParams): InputEntryFunctionData { const { poolId, minPrice, maxPrice, amountA, amountB, fee, isMaxAmountB } = params; const ser = new Serializer(); ser.serializeStr(poolId); ser.serializeOption(); if (amountA > 0 && isMaxAmountB) { ser.serializeU64(Math.round(amountA * 1.1)); // amount_a increase by 10% } else ser.serializeU64(amountA); // amount_a // amount_b increase by 10% ser.serializeU64( amountA == 0 || params.amountB == 0 || params.isMaxAmountB ? amountB : Math.round(amountB * 1.1) ); // fixed_amount_a if (isMaxAmountB) { ser.serializeBool(false); } else { ser.serializeBool(amountA !== 0); } const tickSpacing = getSpacingFromFee(fee); const nearestTick = Math.floor(TICK_BOUND / tickSpacing) * tickSpacing; if (minPrice === Infinity) { ser.serializeU64(toU64(nearestTick)); } else if (minPrice === roundPriceToTickSpacing(EMPTY_MIN_PRICE, fee)) { ser.serializeU64(toU64(-nearestTick)); } else { ser.serializeU64(toU64(priceToTickIndex(minPrice, tickSpacing))); } if (maxPrice === Infinity) { ser.serializeU64(toU64(nearestTick)); } else if (maxPrice === roundPriceToTickSpacing(EMPTY_MIN_PRICE, fee)) { ser.serializeU64(toU64(-nearestTick)); } else { ser.serializeU64(toU64(priceToTickIndex(maxPrice, tickSpacing))); } return { function: `${this._sdk.sdkConfig.contractAddress}::router::add_liquidity`, functionArguments: [ser.toUint8Array()], }; } addStableLiquidity(params: AddStableLiquidityParams): InputEntryFunctionData { const { poolId, amounts } = params; const ser = new Serializer(); ser.serializeStr(poolId); ser.serializeOption(); ser.serializeVector(amounts.map((amount) => new U256(amount))); // min_mint_amount ser.serializeU256(0); return { function: `${this._sdk.sdkConfig.contractAddress}::router::add_liquidity`, functionArguments: [ser.toUint8Array()], }; } createAMMPoolAndAddLiquidity( params: CreateAMMPoolAndAddLiquidityParams ): InputEntryFunctionData { const { tokenAddress, fee, amounts } = params; const ser = new Serializer(); ser.serializeU8(2); ser.serializeVector( tokenAddress.map((address) => AccountAddress.fromString(address)) ); ser.serializeU64(fee); amounts.forEach((amount) => { ser.serializeU64(amount); }); amounts.forEach(() => { ser.serializeU64(0); }); return { function: `${this._sdk.sdkConfig.contractAddress}::router::create_pool_add_liquidity`, functionArguments: [ser.toUint8Array()], }; } createCLMMPoolAndAddLiquidity( params: CreateCLMMPoolAndAddLiquidityParams ): InputEntryFunctionData { const { tokenAddress, fee, amounts, initialPrice, minPrice, maxPrice, isMaxAmountB, } = params; const ser = new Serializer(); ser.serializeU8(3); ser.serializeVector( tokenAddress.map((address) => AccountAddress.fromString(address)) ); // assets ser.serializeU64(fee); const q64 = BigInt(2) ** BigInt(64); const sqrtPInitialPrice = Math.sqrt(initialPrice) * Number(q64); ser.serializeU128(toU64(sqrtPInitialPrice)); let isSingleSide = false; amounts.forEach((amount, index) => { if (!isSingleSide) isSingleSide = amount === 0; if (index === 0 && amount > 0 && isMaxAmountB) { ser.serializeU64(Math.round(amount * 1.1)); } else if (index === 1 && !isSingleSide && !isMaxAmountB) { ser.serializeU64(Math.round(amount * 1.1)); } else { ser.serializeU64(amount); } }); // fixed_amount_a if (isMaxAmountB) { ser.serializeBool(false); } else { ser.serializeBool(amounts?.at(0) !== 0); } const tickSpacing = getSpacingFromFee(fee); const nearestTick = Math.floor(TICK_BOUND / tickSpacing) * tickSpacing; if (minPrice === Infinity) { ser.serializeU64(toU64(nearestTick)); } else if (minPrice === roundPriceToTickSpacing(EMPTY_MIN_PRICE, fee)) { ser.serializeU64(toU64(-nearestTick)); } else { ser.serializeU64(toU64(priceToTickIndex(minPrice, tickSpacing))); } if (maxPrice === Infinity) { ser.serializeU64(toU64(nearestTick)); } else if (maxPrice === roundPriceToTickSpacing(EMPTY_MIN_PRICE, fee)) { ser.serializeU64(toU64(-nearestTick)); } else { ser.serializeU64(toU64(priceToTickIndex(maxPrice, tickSpacing))); } return { function: `${this._sdk.sdkConfig.contractAddress}::router::create_pool_add_liquidity`, functionArguments: [ser.toUint8Array()], }; } createStablePoolAndAddLiquidity( params: CreateStablePoolAndAddLiquidityParams ): InputEntryFunctionData { const { tokenAddress, fee, amounts, amplificationFactor, offpeg_fee_multiplier = 20000000000, } = params; const ser = new Serializer(); ser.serializeU8(4); ser.serializeVector( tokenAddress.map((address) => AccountAddress.fromString(address)) ); // assets ser.serializeU64(fee * 10000); ser.serializeU256(amplificationFactor); // offpeg_fee_multiplier ser.serializeU256(offpeg_fee_multiplier); ser.serializeVector(amounts.map((amount) => new U256(amount))); // min_mint_amount ser.serializeU256(0); return { function: `${this._sdk.sdkConfig.contractAddress}::router::create_pool_add_liquidity`, functionArguments: [ser.toUint8Array()], }; } collectFee(params: CollectFeeParams): InputEntryFunctionData { const ser = new Serializer(); ser.serializeStr(params.poolId); AccountAddress.fromString(params.positionAddr).serialize(ser); return { function: `${this._sdk.sdkConfig.contractAddress}::router::collect_fee`, functionArguments: [ser.toUint8Array()], }; } }