UNPKG

@raydium-io/raydium-sdk-v2

Version:

An SDK for building applications on top of Raydium.

1,434 lines (1,324 loc) 72.7 kB
import { EpochInfo, PublicKey, SystemProgram } from "@solana/web3.js"; import { createTransferInstruction, TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, TransferFee, TransferFeeConfig, createSyncNativeInstruction, } from "@solana/spl-token"; import BN from "bn.js"; import Decimal from "decimal.js"; import { AmmV4Keys, ApiV3PoolInfoConcentratedItem, ApiV3Token, ClmmKeys, PoolKeys } from "@/api"; import { AMM_V4, BigNumberish, CLMM_PROGRAM_ID, CREATE_CPMM_POOL_PROGRAM, fetchMultipleMintInfos, getMultipleAccountsInfoWithCustomFlags, minExpirationTime, parseBigNumberish, solToWSol, WSOLMint, } from "@/common"; import { MakeMultiTxData, MakeTxData } from "@/common/txTool/txTool"; import { InstructionType, TxVersion } from "@/common/txTool/txType"; import { publicKey, struct } from "../../marshmallow"; import { Price, TokenAmount } from "../../module"; import { ClmmInstrument, ClmmParsedRpcData, ComputeClmmPoolInfo, MAX_SQRT_PRICE_X64, MIN_SQRT_PRICE_X64, PoolUtils, ReturnTypeComputeAmountOutBaseOut, ReturnTypeComputeAmountOutFormat, ReturnTypeFetchMultiplePoolTickArrays, SqrtPriceMath, } from "../../raydium/clmm"; import { PoolInfoLayout } from "../../raydium/clmm/layout"; import { CpmmPoolInfoLayout, getPdaPoolAuthority } from "../../raydium/cpmm"; import { ComputeAmountOutParam, getLiquidityAssociatedAuthority, liquidityStateV4Layout, toAmmComputePoolInfo, } from "../../raydium/liquidity"; import { ComputeBudgetConfig, ReturnTypeFetchMultipleMintInfos, TransferAmountFee } from "../../raydium/type"; import { closeAccountInstruction, createWSolAccountInstructions } from "../account/instruction"; import { TokenAccount } from "../account/types"; import { CpmmComputeData } from "../cpmm"; import { AmmRpcData } from "../liquidity"; import ModuleBase, { ModuleBaseProps } from "../moduleBase"; import { Market, MARKET_STATE_LAYOUT_V3 } from "../serum"; import { toApiV3Token, toToken, toTokenAmount } from "../token"; import { makeSwapInstruction } from "./instrument"; import { BasicPoolInfo, ComputeAmountOutAmmLayout, ComputeAmountOutLayout, ComputePoolType, ComputeRoutePathType, ReturnTypeFetchMultipleInfo, ReturnTypeGetAllRoute, RoutePathType, } from "./type"; import { buyExactInInstruction, Curve, getPdaCreatorVault, getPdaLaunchpadAuth, getPdaPlatformVault, LaunchpadConfigInfo, LaunchpadPlatformInfo, LaunchpadPoolInfo, PlatformConfig, sellExactInInstruction, SwapInfoReturn, } from "../launchpad"; const ZERO = new BN(0); export default class TradeV2 extends ModuleBase { constructor(params: ModuleBaseProps) { super(params); } private async getWSolAccounts(): Promise<TokenAccount[]> { this.scope.checkOwner(); await this.scope.account.fetchWalletTokenAccounts(); const tokenAccounts = this.scope.account.tokenAccounts.filter((acc) => acc.mint.equals(WSOLMint)); tokenAccounts.sort((a, b) => { if (a.isAssociated) return 1; if (b.isAssociated) return -1; return a.amount.lt(b.amount) ? -1 : 1; }); return tokenAccounts; } public async unWrapWSol<T extends TxVersion>(props: { amount: BigNumberish; computeBudgetConfig?: ComputeBudgetConfig; tokenProgram?: PublicKey; txVersion?: T; feePayer?: PublicKey; }): Promise<MakeTxData<T>> { const { amount, tokenProgram, txVersion = TxVersion.LEGACY, feePayer } = props; const tokenAccounts = await this.getWSolAccounts(); const txBuilder = this.createTxBuilder(feePayer); txBuilder.addCustomComputeBudget(props.computeBudgetConfig); // const ins = await createWSolAccountInstructions({ // connection: this.scope.connection, // owner: this.scope.ownerPubKey, // payer: this.scope.ownerPubKey, // amount: 0, // }); // txBuilder.addInstruction(ins); const amountBN = parseBigNumberish(amount); for (let i = 0; i < tokenAccounts.length; i++) { if (amountBN.gte(tokenAccounts[i].amount)) { txBuilder.addInstruction({ instructions: [ closeAccountInstruction({ tokenAccount: tokenAccounts[i].publicKey!, payer: this.scope.ownerPubKey, owner: this.scope.ownerPubKey, programId: tokenProgram, }), ], }); amountBN.sub(tokenAccounts[i].amount); } else { txBuilder.addInstruction({ instructions: [ closeAccountInstruction({ tokenAccount: tokenAccounts[i].publicKey!, payer: this.scope.ownerPubKey, owner: this.scope.ownerPubKey, programId: tokenProgram, }), ], }); } } return txBuilder.versionBuild({ txVersion }) as Promise<MakeTxData<T>>; } public async wrapWSol<T extends TxVersion>( amount: BigNumberish, tokenProgram?: PublicKey, txVersion?: T, feePayer?: PublicKey, ): Promise<MakeTxData<T>> { // const tokenAccounts = await this.getWSolAccounts(); const txBuilder = this.createTxBuilder(feePayer); const ins = await createWSolAccountInstructions({ connection: this.scope.connection, owner: this.scope.ownerPubKey, payer: this.scope.ownerPubKey, amount, skipCloseAccount: true, }); txBuilder.addInstruction(ins); // if (tokenAccounts.length) { // // already have wsol account // txBuilder.addInstruction({ // instructions: [ // makeTransferInstruction({ // destination: tokenAccounts[0].publicKey!, // source: ins.addresses.newAccount, // amount, // owner: this.scope.ownerPubKey, // tokenProgram, // }), // ], // endInstructions: [ // closeAccountInstruction({ // tokenAccount: ins.addresses.newAccount, // payer: this.scope.ownerPubKey, // owner: this.scope.ownerPubKey, // programId: tokenProgram, // }), // ], // }); // } return txBuilder.versionBuild({ txVersion: txVersion ?? TxVersion.LEGACY }) as Promise<MakeTxData<T>>; } public async swap<T extends TxVersion>({ swapInfo, swapPoolKeys, ownerInfo, computeBudgetConfig, routeProgram, txVersion, feePayer, }: { txVersion: T; swapInfo: ComputeAmountOutLayout; swapPoolKeys?: PoolKeys[]; ownerInfo: { associatedOnly: boolean; checkCreateATAOwner: boolean; }; routeProgram: PublicKey; computeBudgetConfig?: ComputeBudgetConfig; feePayer?: PublicKey; }): Promise<MakeMultiTxData<T>> { const txBuilder = this.createTxBuilder(feePayer); const amountIn = swapInfo.amountIn; const amountOut = swapInfo.amountOut; const useSolBalance = amountIn.amount.token.mint.equals(WSOLMint); const isOutputSol = amountOut.amount.token.mint.equals(WSOLMint); const inputMint = amountIn.amount.token.mint; const outputMint = amountOut.amount.token.mint; const { account: sourceAcc, instructionParams: sourceAccInsParams } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: amountIn.amount.token.isToken2022 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID, mint: inputMint, notUseTokenAccount: useSolBalance, owner: this.scope.ownerPubKey, skipCloseAccount: !useSolBalance, createInfo: useSolBalance ? { payer: this.scope.ownerPubKey, amount: amountIn.amount.raw, } : undefined, associatedOnly: useSolBalance ? false : ownerInfo.associatedOnly, checkCreateATAOwner: ownerInfo.checkCreateATAOwner, }); sourceAccInsParams && txBuilder.addInstruction(sourceAccInsParams); if (sourceAcc === undefined) { throw Error("input account check error"); } let destinationAcc: PublicKey; if (swapInfo.routeType === "route" && !isOutputSol) { destinationAcc = this.scope.account.getAssociatedTokenAccount( outputMint, amountOut.amount.token.isToken2022 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID, ); } else { const { account, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: amountOut.amount.token.isToken2022 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID, mint: outputMint, notUseTokenAccount: isOutputSol, owner: this.scope.ownerPubKey, skipCloseAccount: true, createInfo: { payer: this.scope.ownerPubKey, amount: 0, }, associatedOnly: isOutputSol ? false : ownerInfo.associatedOnly, checkCreateATAOwner: ownerInfo.checkCreateATAOwner, }); destinationAcc = account!; instructionParams && txBuilder.addInstruction(instructionParams); } if (isOutputSol) { txBuilder.addInstruction({ endInstructions: [ closeAccountInstruction({ owner: this.scope.ownerPubKey, payer: this.scope.ownerPubKey, tokenAccount: destinationAcc, programId: TOKEN_PROGRAM_ID, }), ], endInstructionTypes: [InstructionType.CloseAccount], }); } let routeTokenAcc: PublicKey | undefined = undefined; if (swapInfo.routeType === "route") { const middleMint = swapInfo.middleToken; routeTokenAcc = this.scope.account.getAssociatedTokenAccount( middleMint.mint, middleMint.isToken2022 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID, ); } const poolKeys = swapPoolKeys ? swapPoolKeys : await this.computePoolToPoolKeys({ pools: swapInfo.poolInfoList }); const swapIns = makeSwapInstruction({ routeProgram, inputMint, swapInfo: { ...swapInfo, poolInfo: [...swapInfo.poolInfoList], poolKey: poolKeys, outputMint, }, ownerInfo: { wallet: this.scope.ownerPubKey, sourceToken: sourceAcc, routeToken: routeTokenAcc, destinationToken: destinationAcc!, }, }); if (swapInfo.feeConfig !== undefined) { const checkTxBuilder = this.createTxBuilder(); checkTxBuilder.addInstruction({ instructions: [ createTransferInstruction( sourceAcc, swapInfo.feeConfig.feeAccount, this.scope.ownerPubKey, swapInfo.feeConfig.feeAmount.toNumber(), ), ], instructionTypes: [InstructionType.TransferAmount], }); checkTxBuilder.addInstruction(swapIns); const { transactions } = txVersion === TxVersion.V0 ? await checkTxBuilder.sizeCheckBuildV0() : await checkTxBuilder.sizeCheckBuild(); if (transactions.length < 2) { txBuilder.addInstruction({ instructions: [ createTransferInstruction( sourceAcc, swapInfo.feeConfig.feeAccount, this.scope.ownerPubKey, swapInfo.feeConfig.feeAmount.toNumber(), ), ], instructionTypes: [InstructionType.TransferAmount], }); } } txBuilder.addInstruction(swapIns); if (txVersion === TxVersion.V0) return txBuilder.sizeCheckBuildV0({ computeBudgetConfig, address: swapIns.address }) as Promise< MakeMultiTxData<T> >; return txBuilder.sizeCheckBuild({ computeBudgetConfig, address: swapIns.address }) as Promise<MakeMultiTxData<T>>; } public async swapClmmToLaunchMint<T extends TxVersion>({ inputMint, inputAmount, fixClmmOut = false, clmmPoolId, launchPoolId, priceLimit, slippage = 0.01, shareFeeRate = new BN(0), shareFeeReceiver, launchPlatformInfo, slot, mintInfo, epochInfo: propsEpochInfo, ownerInfo = { useSOLBalance: true }, checkCreateATAOwner = false, computeBudgetConfig, txVersion, }: { inputMint: string | PublicKey; inputAmount: BN; fixClmmOut?: boolean; clmmPoolId: string | PublicKey; launchPoolId: string | PublicKey; priceLimit?: Decimal; epochInfo?: EpochInfo; slippage: number; // from 0~1 shareFeeRate?: BN; shareFeeReceiver?: PublicKey; launchPlatformInfo?: Pick<LaunchpadPlatformInfo, "feeRate" | "creatorFeeRate">; slot?: number; mintInfo?: ApiV3Token; ownerInfo?: { useSOLBalance?: boolean; feePayer?: PublicKey; }; checkCreateATAOwner?: boolean; computeBudgetConfig?: ComputeBudgetConfig; txVersion: T; }): Promise< MakeTxData< T, { routes: { mint: PublicKey; amount: BN; decimal: number }[]; outAmount: BN; minOutAmount: BN; } > > { const feePayer = ownerInfo?.feePayer || this.scope.ownerPubKey; const epochInfo = propsEpochInfo ?? (await this.scope.fetchEpochInfo()); const { clmmPoolData, clmmComputeAmount: { maxClmmAmountIn, clmmAmountOut, remainingAccounts }, launchPoolInfo, launchAuthProgramId, launchSwapInfo, outAmount, minOutAmount, } = await this.computeClmmToLaunchAmount({ inputMint, inputAmount, fixClmmOut, clmmPoolId, launchPoolId, slippage, epochInfo, shareFeeRate, launchPlatformInfo, slot, mintInfo, }); const baseIn = inputMint.toString() === clmmPoolData.poolInfo.mintA.address; const mintAUseSOLBalance = ownerInfo.useSOLBalance && clmmPoolData.poolInfo.mintA.address === WSOLMint.toBase58(); const mintBUseSOLBalance = ownerInfo.useSOLBalance && clmmPoolData.poolInfo.mintB.address === WSOLMint.toBase58(); const tokenAccountMap: Record<string, PublicKey> = {}; let sqrtPriceLimitX64: BN; if (!priceLimit || priceLimit.equals(new Decimal(0))) { sqrtPriceLimitX64 = baseIn ? MIN_SQRT_PRICE_X64.add(new BN(1)) : MAX_SQRT_PRICE_X64.sub(new BN(1)); } else { sqrtPriceLimitX64 = SqrtPriceMath.priceToSqrtPriceX64( priceLimit, clmmPoolData.poolInfo.mintA.decimals, clmmPoolData.poolInfo.mintB.decimals, ); } const txBuilder = this.createTxBuilder(feePayer); const [clmmMintA, clmmMintB] = [ new PublicKey(clmmPoolData.poolInfo.mintA.address), new PublicKey(clmmPoolData.poolInfo.mintB.address), ]; const [clmmMintAProgram, clmmMintBProgram] = [ new PublicKey(clmmPoolData.poolInfo.mintA.programId), new PublicKey(clmmPoolData.poolInfo.mintB.programId), ]; const ownerTokenAccountA = this.scope.account.getAssociatedTokenAccount(clmmMintA, clmmMintAProgram); const ownerTokenAccountB = this.scope.account.getAssociatedTokenAccount(clmmMintB, clmmMintBProgram); txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, ownerTokenAccountA, this.scope.ownerPubKey, clmmMintA, clmmMintAProgram, ), createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, ownerTokenAccountB, this.scope.ownerPubKey, clmmMintB, clmmMintBProgram, ), ], }); if ((baseIn && mintAUseSOLBalance) || (!baseIn && mintBUseSOLBalance)) { txBuilder.addInstruction({ instructions: [ SystemProgram.transfer({ fromPubkey: this.scope.ownerPubKey, toPubkey: baseIn ? ownerTokenAccountA : ownerTokenAccountB, lamports: BigInt(maxClmmAmountIn.toString()), }), createSyncNativeInstruction(baseIn ? ownerTokenAccountA : ownerTokenAccountB), ], }); } tokenAccountMap[clmmPoolData.poolInfo.mintA.address] = ownerTokenAccountA; tokenAccountMap[clmmPoolData.poolInfo.mintB.address] = ownerTokenAccountB; if (!ownerTokenAccountA || !ownerTokenAccountB) this.logAndCreateError("user do not have token account", { ownerTokenAccountA, ownerTokenAccountB, }); txBuilder.addInstruction( fixClmmOut ? ClmmInstrument.makeSwapBaseOutInstructions({ poolInfo: clmmPoolData.poolInfo, poolKeys: clmmPoolData.poolKeys, observationId: clmmPoolData.computePoolInfo.observationId, ownerInfo: { wallet: this.scope.ownerPubKey, tokenAccountA: ownerTokenAccountA!, tokenAccountB: ownerTokenAccountB!, }, outputMint: baseIn ? clmmMintB : clmmMintA, amountOut: clmmAmountOut, amountInMax: maxClmmAmountIn, sqrtPriceLimitX64, remainingAccounts, }) : ClmmInstrument.makeSwapBaseInInstructions({ poolInfo: clmmPoolData.poolInfo, poolKeys: clmmPoolData.poolKeys, observationId: clmmPoolData.computePoolInfo.observationId, ownerInfo: { wallet: this.scope.ownerPubKey, tokenAccountA: ownerTokenAccountA!, tokenAccountB: ownerTokenAccountB!, }, inputMint: new PublicKey(inputMint), amountIn: inputAmount, amountOutMin: clmmAmountOut, sqrtPriceLimitX64, remainingAccounts, }), ); const launchMintAProgram = launchPoolInfo.mintProgramFlag === 0 ? TOKEN_PROGRAM_ID : TOKEN_2022_PROGRAM_ID; const launchTokenAccountA = this.scope.account.getAssociatedTokenAccount(launchPoolInfo.mintA, launchMintAProgram); let launchTokenAccountB = tokenAccountMap[launchPoolInfo.mintB.toBase58()]; txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, launchTokenAccountA, this.scope.ownerPubKey, launchPoolInfo.mintA, launchMintAProgram, ), ], }); if (!launchTokenAccountB) { const mintBUseSol = launchPoolInfo.mintB.equals(WSOLMint); const { account, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: TOKEN_PROGRAM_ID, mint: launchPoolInfo.mintB, notUseTokenAccount: mintBUseSol, owner: this.scope.ownerPubKey, skipCloseAccount: !mintBUseSol, createInfo: mintBUseSol ? { payer: this.scope.ownerPubKey!, amount: clmmAmountOut, } : undefined, associatedOnly: false, checkCreateATAOwner, }); launchTokenAccountB = account!; instructionParams && txBuilder.addInstruction(instructionParams); } txBuilder.addInstruction({ instructions: [ buyExactInInstruction( launchPoolInfo.programId, this.scope.ownerPubKey, launchAuthProgramId, launchPoolInfo.configId, launchPoolInfo.platformId, new PublicKey(launchPoolId), launchTokenAccountA, launchTokenAccountB, launchPoolInfo.vaultA, launchPoolInfo.vaultB, launchPoolInfo.mintA, launchPoolInfo.mintB, launchMintAProgram, TOKEN_PROGRAM_ID, getPdaPlatformVault(launchPoolInfo.programId, launchPoolInfo.platformId, launchPoolInfo.mintB).publicKey, getPdaCreatorVault(launchPoolInfo.programId, launchPoolInfo.creator, launchPoolInfo.mintB).publicKey, launchSwapInfo.amountB.lt(clmmAmountOut) ? launchSwapInfo.amountB : clmmAmountOut, minOutAmount, shareFeeRate, shareFeeReceiver, ), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); return txBuilder.versionBuild({ txVersion, extInfo: { routes: [ { mint: new PublicKey(inputMint), amount: fixClmmOut ? maxClmmAmountIn : inputAmount, decimal: clmmPoolData.poolInfo[baseIn ? "mintA" : "mintB"].decimals, }, { mint: baseIn ? clmmMintB : clmmMintA, amount: clmmAmountOut, decimal: clmmPoolData.poolInfo[baseIn ? "mintB" : "mintA"].decimals, }, { mint: launchPoolInfo.mintA, amount: outAmount, decimal: launchPoolInfo.mintDecimalsA, }, ], outAmount, minOutAmount, }, }) as Promise< MakeTxData< T, { routes: { mint: PublicKey; amount: BN; decimal: number }[]; outAmount: BN; minOutAmount: BN; } > >; } public async computeClmmToLaunchAmount({ inputMint, inputAmount, fixClmmOut = false, clmmPoolId, launchPoolId, slippage: propsSlippage, epochInfo, shareFeeRate = new BN(0), clmmPoolData: propsClmmPoolData, launchPoolInfo: propsLaunchPoolInfo, launchPlatformInfo: propsLaunchPlatformInfo, slot, mintInfo: propsMintInfo, }: { clmmPoolId: string | PublicKey; launchPoolId: string | PublicKey; inputMint: string | PublicKey; inputAmount: BN; fixClmmOut?: boolean; slippage: number; epochInfo?: EpochInfo; shareFeeRate?: BN; clmmPoolData?: { poolInfo: ApiV3PoolInfoConcentratedItem; poolKeys: ClmmKeys; computePoolInfo: ComputeClmmPoolInfo; tickData: ReturnTypeFetchMultiplePoolTickArrays; }; launchPoolInfo?: LaunchpadPoolInfo & { programId: PublicKey; configInfo: LaunchpadConfigInfo }; launchPlatformInfo?: Pick<LaunchpadPlatformInfo, "feeRate" | "creatorFeeRate">; slot?: number; mintInfo?: ApiV3Token; }): Promise<{ clmmPoolData: { poolInfo: ApiV3PoolInfoConcentratedItem; poolKeys: ClmmKeys; computePoolInfo: ComputeClmmPoolInfo; tickData: ReturnTypeFetchMultiplePoolTickArrays; }; clmmComputeAmount: { maxClmmAmountIn: BN; clmmAmountOut: BN; remainingAccounts: PublicKey[] }; clmmComputeInfo: ReturnTypeComputeAmountOutBaseOut | ReturnTypeComputeAmountOutFormat; launchPoolInfo: LaunchpadPoolInfo & { programId: PublicKey; configInfo: LaunchpadConfigInfo }; launchAuthProgramId: PublicKey; outAmount: BN; minOutAmount: BN; launchSwapInfo: SwapInfoReturn; launchMintTransferFeeConfig?: TransferFeeConfig; }> { // split slippage for clmm swap and launch buy const slippage = propsSlippage > 0 ? new Decimal(propsSlippage).div(2).toDecimalPlaces(4, Decimal.ROUND_DOWN).toNumber() : propsSlippage; const clmmPoolData = propsClmmPoolData ?? (await this.scope.clmm.getPoolInfoFromRpc(clmmPoolId.toString())); if ( inputMint.toString() !== clmmPoolData.poolInfo.mintA.address && inputMint.toString() !== clmmPoolData.poolInfo.mintB.address ) throw new Error("input mint does not match clmm pool mints, please check"); const baseIn = inputMint.toString() === clmmPoolData.poolInfo.mintA.address; const tokenOut = clmmPoolData.poolInfo[baseIn ? "mintB" : "mintA"]; const clmmComputeAmount = fixClmmOut ? await PoolUtils.computeAmountIn({ poolInfo: clmmPoolData.computePoolInfo, tickArrayCache: clmmPoolData.tickData[clmmPoolId.toString()], amountOut: inputAmount, baseMint: new PublicKey(clmmPoolData.poolInfo[baseIn ? "mintB" : "mintA"].address), slippage, epochInfo: epochInfo ?? (await this.scope.fetchEpochInfo()), }) : await PoolUtils.computeAmountOutFormat({ poolInfo: clmmPoolData.computePoolInfo, tickArrayCache: clmmPoolData.tickData[clmmPoolId.toString()], amountIn: inputAmount, tokenOut, slippage, epochInfo: epochInfo ?? (await this.scope.fetchEpochInfo()), }); let launchPoolInfo = propsLaunchPoolInfo; if (!launchPoolInfo) launchPoolInfo = await this.scope.launchpad.getRpcPoolInfo({ poolId: new PublicKey(launchPoolId) }); if (tokenOut.address !== launchPoolInfo.mintB.toBase58()) throw new Error(`clmm swap mint(${tokenOut.address}) != launch pool mintB(${launchPoolInfo.mintB.toBase58()})`); let platformInfo = propsLaunchPlatformInfo; if (!platformInfo) { const data = await this.scope.connection.getAccountInfo(launchPoolInfo.platformId); platformInfo = PlatformConfig.decode(data!.data); } const mintInfo = propsMintInfo ?? (await this.scope.token.getTokenInfo(launchPoolInfo.mintA)); const authProgramId = getPdaLaunchpadAuth(launchPoolInfo.programId).publicKey; const launchMintTransferFeeConfig = mintInfo.extensions.feeConfig ? { transferFeeConfigAuthority: PublicKey.default, withdrawWithheldAuthority: PublicKey.default, withheldAmount: BigInt(0), olderTransferFee: { epoch: BigInt(mintInfo.extensions.feeConfig.olderTransferFee.epoch ?? epochInfo?.epoch ?? 0), maximumFee: BigInt(mintInfo.extensions.feeConfig.olderTransferFee.maximumFee), transferFeeBasisPoints: mintInfo.extensions.feeConfig.olderTransferFee.transferFeeBasisPoints, }, newerTransferFee: { epoch: BigInt(mintInfo.extensions.feeConfig.newerTransferFee.epoch ?? epochInfo?.epoch ?? 0), maximumFee: BigInt(mintInfo.extensions.feeConfig.newerTransferFee.maximumFee), transferFeeBasisPoints: mintInfo.extensions.feeConfig.newerTransferFee.transferFeeBasisPoints, }, } : undefined; const launchBuyAmount = fixClmmOut ? inputAmount : (clmmComputeAmount as ReturnTypeComputeAmountOutFormat).minAmountOut.amount.raw; const launchSwapInfo = Curve.buyExactIn({ poolInfo: launchPoolInfo, amountB: launchBuyAmount, protocolFeeRate: launchPoolInfo.configInfo.tradeFeeRate, platformFeeRate: platformInfo.feeRate, curveType: launchPoolInfo.configInfo.curveType, shareFeeRate, creatorFeeRate: platformInfo.creatorFeeRate, transferFeeConfigA: launchMintTransferFeeConfig, slot: slot ?? (await this.scope.connection.getSlot()), }); const outAmount = launchSwapInfo.amountA.amount.sub(launchSwapInfo.amountA.fee ?? new BN(0)); const decimalAmountA = new Decimal(outAmount.toString()); const SLIPPAGE_UNIT = new BN(10000); const multiplier = slippage ? new Decimal(SLIPPAGE_UNIT.sub(new BN(slippage * 10000)).toNumber() / SLIPPAGE_UNIT.toNumber()).clampedTo(0, 1) : new Decimal(1); return { clmmPoolData, clmmComputeAmount: { maxClmmAmountIn: fixClmmOut ? (clmmComputeAmount as ReturnTypeComputeAmountOutBaseOut).maxAmountIn.amount : inputAmount, clmmAmountOut: launchBuyAmount, remainingAccounts: clmmComputeAmount.remainingAccounts, }, clmmComputeInfo: clmmComputeAmount, launchPoolInfo, launchAuthProgramId: authProgramId, launchMintTransferFeeConfig, launchSwapInfo, outAmount: launchSwapInfo.amountA.amount.sub(launchSwapInfo.amountA.fee ?? new BN(0)), minOutAmount: new BN(decimalAmountA.mul(multiplier).toFixed(0)), }; } public async swapLaunchMintToClmm<T extends TxVersion>({ inputAmount, clmmPoolId, launchPoolId, priceLimit, slippage = 0.01, shareFeeRate = new BN(0), shareFeeReceiver, ownerInfo = { useSOLBalance: true }, checkCreateATAOwner = false, computeBudgetConfig, txVersion, }: { inputAmount: BN; clmmPoolId: string | PublicKey; launchPoolId: string | PublicKey; priceLimit?: Decimal; slippage: number; // from 0~1 shareFeeRate?: BN; shareFeeReceiver?: PublicKey; ownerInfo?: { useSOLBalance?: boolean; feePayer?: PublicKey; }; checkCreateATAOwner?: boolean; computeBudgetConfig?: ComputeBudgetConfig; txVersion: T; }): Promise< MakeTxData< T, { routes: { mint: PublicKey; amount: BN; decimal: number }[]; outAmount: BN; minOutAmount: BN; } > > { const feePayer = ownerInfo?.feePayer || this.scope.ownerPubKey; const epochInfo = await this.scope.fetchEpochInfo(); const { clmmPoolData, clmmComputeAmount: { remainingAccounts }, launchPoolInfo, launchAuthProgramId, launchSwapInfo, minLaunchOutAmount, outAmount, minOutAmount, } = await this.computeLaunchToClmmAmount({ inputAmount, clmmPoolId, launchPoolId, slippage, epochInfo, shareFeeRate, }); const txBuilder = this.createTxBuilder(feePayer); const tokenAccountMap: Record<string, PublicKey> = {}; const launchMintAProgram = launchPoolInfo.mintProgramFlag === 0 ? TOKEN_PROGRAM_ID : TOKEN_2022_PROGRAM_ID; const { account: launchTokenAccountA } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: launchMintAProgram, mint: launchPoolInfo.mintA, notUseTokenAccount: false, owner: this.scope.ownerPubKey, skipCloseAccount: true, createInfo: undefined, associatedOnly: true, checkCreateATAOwner, }); if (!launchTokenAccountA) throw new Error(`do not have launch mint(${launchPoolInfo.mintA.toString()}) token account`); const mintBUseSol = launchPoolInfo.mintB.equals(WSOLMint); const { account: launchTokenAccountB, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: TOKEN_PROGRAM_ID, mint: launchPoolInfo.mintB, notUseTokenAccount: mintBUseSol, owner: this.scope.ownerPubKey, skipCloseAccount: !mintBUseSol, createInfo: { payer: this.scope.ownerPubKey!, amount: 0, }, associatedOnly: false, checkCreateATAOwner, }); instructionParams && txBuilder.addInstruction(instructionParams); if (!launchTokenAccountB) throw new Error(`do not have launch mint(${launchPoolInfo.mintA.toString()}) token account`); tokenAccountMap[launchPoolInfo.mintB.toBase58()] = launchTokenAccountB; txBuilder.addInstruction({ instructions: [ sellExactInInstruction( launchPoolInfo.programId, this.scope.ownerPubKey, launchAuthProgramId, launchPoolInfo.configId, launchPoolInfo.platformId, new PublicKey(launchPoolId), launchTokenAccountA, launchTokenAccountB, launchPoolInfo.vaultA, launchPoolInfo.vaultB, launchPoolInfo.mintA, launchPoolInfo.mintB, launchMintAProgram, TOKEN_PROGRAM_ID, getPdaPlatformVault(launchPoolInfo.programId, launchPoolInfo.platformId, launchPoolInfo.mintB).publicKey, getPdaCreatorVault(launchPoolInfo.programId, launchPoolInfo.creator, launchPoolInfo.mintB).publicKey, launchSwapInfo.amountA.amount.lt(inputAmount) ? launchSwapInfo.amountA.amount : inputAmount, minLaunchOutAmount, shareFeeRate, shareFeeReceiver, ), ], }); const baseIn = launchPoolInfo.mintB.toString() === clmmPoolData.poolInfo.mintA.address; const mintAUseSOLBalance = ownerInfo.useSOLBalance && clmmPoolData.poolInfo.mintA.address === WSOLMint.toBase58(); const mintBUseSOLBalance = ownerInfo.useSOLBalance && clmmPoolData.poolInfo.mintB.address === WSOLMint.toBase58(); let sqrtPriceLimitX64: BN; if (!priceLimit || priceLimit.equals(new Decimal(0))) { sqrtPriceLimitX64 = baseIn ? MIN_SQRT_PRICE_X64.add(new BN(1)) : MAX_SQRT_PRICE_X64.sub(new BN(1)); } else { sqrtPriceLimitX64 = SqrtPriceMath.priceToSqrtPriceX64( priceLimit, clmmPoolData.poolInfo.mintA.decimals, clmmPoolData.poolInfo.mintB.decimals, ); } const [clmmMintA, clmmMintB] = [ new PublicKey(clmmPoolData.poolInfo.mintA.address), new PublicKey(clmmPoolData.poolInfo.mintB.address), ]; const [clmmMintAProgram, clmmMintBProgram] = [ new PublicKey(clmmPoolData.poolInfo.mintA.programId), new PublicKey(clmmPoolData.poolInfo.mintB.programId), ]; let ownerTokenAccountA = mintAUseSOLBalance ? undefined : this.scope.account.getAssociatedTokenAccount(clmmMintA, clmmMintAProgram); let ownerTokenAccountB = mintBUseSOLBalance ? undefined : this.scope.account.getAssociatedTokenAccount(clmmMintB, clmmMintBProgram); // this means mintA is wsol if (!ownerTokenAccountA) { const { account, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: clmmMintAProgram, mint: clmmMintA, notUseTokenAccount: true, owner: this.scope.ownerPubKey, skipCloseAccount: false, createInfo: { payer: ownerInfo.feePayer || this.scope.ownerPubKey, amount: baseIn ? inputAmount : 0, }, associatedOnly: false, checkCreateATAOwner, }); ownerTokenAccountA = account!; instructionParams && txBuilder.addInstruction(instructionParams); } // this means mintB is wsol if (!ownerTokenAccountB) { const { account, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: clmmMintBProgram, mint: clmmMintB, notUseTokenAccount: true, owner: this.scope.ownerPubKey, skipCloseAccount: false, createInfo: { payer: ownerInfo.feePayer || this.scope.ownerPubKey, amount: baseIn ? 0 : inputAmount, }, associatedOnly: false, checkCreateATAOwner, }); ownerTokenAccountB = account!; instructionParams && txBuilder.addInstruction(instructionParams); } tokenAccountMap[clmmPoolData.poolInfo.mintA.address] = ownerTokenAccountA; tokenAccountMap[clmmPoolData.poolInfo.mintB.address] = ownerTokenAccountB; if (!ownerTokenAccountA || !ownerTokenAccountB) this.logAndCreateError("user do not have token account", { ownerTokenAccountA, ownerTokenAccountB, }); txBuilder.addInstruction( ClmmInstrument.makeSwapBaseInInstructions({ poolInfo: clmmPoolData.poolInfo, poolKeys: clmmPoolData.poolKeys, observationId: clmmPoolData.computePoolInfo.observationId, ownerInfo: { wallet: this.scope.ownerPubKey, tokenAccountA: ownerTokenAccountA!, tokenAccountB: ownerTokenAccountB!, }, inputMint: new PublicKey(clmmPoolData.poolKeys[baseIn ? "mintA" : "mintB"].address), amountIn: minLaunchOutAmount, amountOutMin: minOutAmount, sqrtPriceLimitX64, remainingAccounts, }), ); txBuilder.addCustomComputeBudget(computeBudgetConfig); return txBuilder.versionBuild({ txVersion, extInfo: { routes: [ { mint: launchPoolInfo.mintA, amount: inputAmount, decimal: launchPoolInfo.mintDecimalsA, }, { mint: launchPoolInfo.mintB, amount: minLaunchOutAmount, decimal: launchPoolInfo.mintDecimalsB, }, { mint: new PublicKey(clmmPoolData.poolKeys[baseIn ? "mintB" : "mintA"].address), amount: outAmount, decimal: clmmPoolData.poolKeys[baseIn ? "mintB" : "mintA"].decimals, }, ], outAmount, minOutAmount, }, }) as Promise< MakeTxData< T, { routes: { mint: PublicKey; amount: BN; decimal: number }[]; outAmount: BN; minOutAmount: BN; } > >; } public async computeLaunchToClmmAmount({ inputAmount, clmmPoolId, launchPoolId, slippage: propsSlippage, epochInfo, shareFeeRate = new BN(0), clmmPoolData: propsClmmPoolData, launchPoolInfo: propsLaunchPoolInfo, launchPlatformInfo: propsLaunchPlatformInfo, }: { clmmPoolId: string | PublicKey; launchPoolId: string | PublicKey; inputAmount: BN; slippage: number; epochInfo?: EpochInfo; shareFeeRate?: BN; clmmPoolData?: { poolInfo: ApiV3PoolInfoConcentratedItem; poolKeys: ClmmKeys; computePoolInfo: ComputeClmmPoolInfo; tickData: ReturnTypeFetchMultiplePoolTickArrays; }; launchPoolInfo?: LaunchpadPoolInfo & { programId: PublicKey; configInfo: LaunchpadConfigInfo }; launchPlatformInfo?: LaunchpadPlatformInfo; }): Promise<{ clmmPoolData: { poolInfo: ApiV3PoolInfoConcentratedItem; poolKeys: ClmmKeys; computePoolInfo: ComputeClmmPoolInfo; tickData: ReturnTypeFetchMultiplePoolTickArrays; }; clmmComputeAmount: ReturnTypeComputeAmountOutFormat; launchPoolInfo: LaunchpadPoolInfo & { programId: PublicKey; configInfo: LaunchpadConfigInfo }; launchAuthProgramId: PublicKey; minLaunchOutAmount: BN; outAmount: BN; minOutAmount: BN; launchSwapInfo: SwapInfoReturn; launchMintTransferFeeConfig?: TransferFeeConfig; }> { // split slippage for clmm swap and launch buy const slippage = propsSlippage > 0 ? new Decimal(propsSlippage).div(2).toDecimalPlaces(4, Decimal.ROUND_DOWN).toNumber() : propsSlippage; let launchPoolInfo = propsLaunchPoolInfo; if (!launchPoolInfo) launchPoolInfo = await this.scope.launchpad.getRpcPoolInfo({ poolId: new PublicKey(launchPoolId) }); const inputMint = launchPoolInfo.mintB; const clmmPoolData = propsClmmPoolData ?? (await this.scope.clmm.getPoolInfoFromRpc(clmmPoolId.toString())); if ( inputMint.toString() !== clmmPoolData.poolInfo.mintA.address && inputMint.toString() !== clmmPoolData.poolInfo.mintB.address ) throw new Error("input mint does not match clmm pool mints, please check"); const baseIn = inputMint.toString() === clmmPoolData.poolInfo.mintA.address; const tokenOut = clmmPoolData.poolInfo[baseIn ? "mintB" : "mintA"]; let platformInfo = propsLaunchPlatformInfo; if (!platformInfo) { const data = await this.scope.connection.getAccountInfo(launchPoolInfo.platformId); platformInfo = PlatformConfig.decode(data!.data); } const mintInfo = await this.scope.token.getTokenInfo(launchPoolInfo.mintA); const authProgramId = getPdaLaunchpadAuth(launchPoolInfo.programId).publicKey; const launchMintTransferFeeConfig = mintInfo.extensions.feeConfig ? { transferFeeConfigAuthority: PublicKey.default, withdrawWithheldAuthority: PublicKey.default, withheldAmount: BigInt(0), olderTransferFee: { epoch: BigInt(mintInfo.extensions.feeConfig.olderTransferFee.epoch ?? epochInfo?.epoch ?? 0), maximumFee: BigInt(mintInfo.extensions.feeConfig.olderTransferFee.maximumFee), transferFeeBasisPoints: mintInfo.extensions.feeConfig.olderTransferFee.transferFeeBasisPoints, }, newerTransferFee: { epoch: BigInt(mintInfo.extensions.feeConfig.newerTransferFee.epoch ?? epochInfo?.epoch ?? 0), maximumFee: BigInt(mintInfo.extensions.feeConfig.newerTransferFee.maximumFee), transferFeeBasisPoints: mintInfo.extensions.feeConfig.newerTransferFee.transferFeeBasisPoints, }, } : undefined; const launchSwapInfo = Curve.sellExactIn({ poolInfo: launchPoolInfo, amountA: inputAmount, protocolFeeRate: launchPoolInfo.configInfo.tradeFeeRate, platformFeeRate: platformInfo.feeRate, curveType: launchPoolInfo.configInfo.curveType, shareFeeRate, creatorFeeRate: platformInfo.creatorFeeRate, transferFeeConfigA: launchMintTransferFeeConfig, slot: await this.scope.connection.getSlot(), }); const outAmount = launchSwapInfo.amountB; const decimalAmountB = new Decimal(outAmount.toString()); const SLIPPAGE_UNIT = new BN(10000); const multiplier = slippage ? new Decimal(SLIPPAGE_UNIT.sub(new BN(slippage * 10000)).toNumber() / SLIPPAGE_UNIT.toNumber()).clampedTo(0, 1) : new Decimal(1); const minLaunchOutAmount = new BN(decimalAmountB.mul(multiplier).toFixed(0)); const clmmComputeAmount = await PoolUtils.computeAmountOutFormat({ poolInfo: clmmPoolData.computePoolInfo, tickArrayCache: clmmPoolData.tickData[clmmPoolId.toString()], amountIn: minLaunchOutAmount, tokenOut, slippage, epochInfo: epochInfo ?? (await this.scope.fetchEpochInfo()), }); return { clmmPoolData, clmmComputeAmount, launchPoolInfo, launchAuthProgramId: authProgramId, launchMintTransferFeeConfig, launchSwapInfo, minLaunchOutAmount, outAmount: clmmComputeAmount.amountOut.amount.raw, minOutAmount: clmmComputeAmount.minAmountOut.amount.raw, }; } // get all amm/clmm/cpmm pools data only with id and mint public async fetchRoutePoolBasicInfo(programIds?: { amm: PublicKey; clmm: PublicKey; cpmm: PublicKey }): Promise<{ ammPools: BasicPoolInfo[]; clmmPools: BasicPoolInfo[]; cpmmPools: BasicPoolInfo[]; }> { const { amm = AMM_V4, clmm = CLMM_PROGRAM_ID, cpmm = CREATE_CPMM_POOL_PROGRAM } = programIds || {}; const ammPoolsData = await this.scope.connection.getProgramAccounts(amm, { dataSlice: { offset: liquidityStateV4Layout.offsetOf("baseMint"), length: 64 }, }); const layoutAmm = struct([publicKey("baseMint"), publicKey("quoteMint")]); const ammData = ammPoolsData.map((data) => ({ id: data.pubkey, version: 4, mintA: layoutAmm.decode(data.account.data).baseMint, mintB: layoutAmm.decode(data.account.data).quoteMint, })); const layout = struct([publicKey("mintA"), publicKey("mintB")]); const clmmPoolsData = await this.scope.connection.getProgramAccounts(clmm, { filters: [{ dataSize: PoolInfoLayout.span }], dataSlice: { offset: PoolInfoLayout.offsetOf("mintA"), length: 64 }, }); const clmmData = clmmPoolsData.map((data) => { const clmm = layout.decode(data.account.data); return { id: data.pubkey, version: 6, mintA: clmm.mintA, mintB: clmm.mintB, }; }); const cpmmPools = await this.scope.connection.getProgramAccounts(cpmm, { dataSlice: { offset: CpmmPoolInfoLayout.offsetOf("mintA"), length: 64 }, }); const cpmmData = cpmmPools.map((data) => { const clmm = layout.decode(data.account.data); return { id: data.pubkey, version: 7, mintA: clmm.mintA, mintB: clmm.mintB, }; }); return { clmmPools: clmmData, ammPools: ammData, cpmmPools: cpmmData, }; } // get pools with in routes public getAllRoute({ inputMint, outputMint, clmmPools, ammPools, cpmmPools, }: { inputMint: PublicKey; outputMint: PublicKey; clmmPools: BasicPoolInfo[]; ammPools: BasicPoolInfo[]; cpmmPools: BasicPoolInfo[]; }): ReturnTypeGetAllRoute { inputMint = inputMint.toString() === PublicKey.default.toString() ? WSOLMint : inputMint; outputMint = outputMint.toString() === PublicKey.default.toString() ? WSOLMint : outputMint; const needSimulate: { [poolKey: string]: BasicPoolInfo } = {}; const needTickArray: { [poolKey: string]: BasicPoolInfo } = {}; const cpmmPoolList: { [poolKey: string]: BasicPoolInfo } = {}; const directPath: BasicPoolInfo[] = []; const routePathDict: RoutePathType = {}; // {[route mint: string]: {in: [] , out: []}} for (const itemClmmPool of clmmPools ?? []) { if ( (itemClmmPool.mintA.equals(inputMint) && itemClmmPool.mintB.equals(outputMint)) || (itemClmmPool.mintA.equals(outputMint) && itemClmmPool.mintB.equals(inputMint)) ) { directPath.push(itemClmmPool); needTickArray[itemClmmPool.id.toString()] = itemClmmPool; } if (itemClmmPool.mintA.equals(inputMint)) { const t = itemClmmPool.mintB.toString(); if (routePathDict[t] === undefined) routePathDict[t] = { mintProgram: TOKEN_PROGRAM_ID, // to fetch later in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[t].in.push(itemClmmPool); } if (itemClmmPool.mintB.equals(inputMint)) { const t = itemClmmPool.mintA.toString(); if (routePathDict[t] === undefined) routePathDict[t] = { mintProgram: TOKEN_PROGRAM_ID, // to fetch later in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[t].in.push(itemClmmPool); } if (itemClmmPool.mintA.equals(outputMint)) { const t = itemClmmPool.mintB.toString(); if (routePathDict[t] === undefined) routePathDict[t] = { mintProgram: TOKEN_PROGRAM_ID, // to fetch later in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[t].out.push(itemClmmPool); } if (itemClmmPool.mintB.equals(outputMint)) { const t = itemClmmPool.mintA.toString(); if (routePathDict[t] === undefined) routePathDict[t] = { mintProgram: TOKEN_PROGRAM_ID, // to fetch later in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[t].out.push(itemClmmPool); } } const addLiquidityPools: BasicPoolInfo[] = []; for (const itemAmmPool of ammPools) { if ( (itemAmmPool.mintA.equals(inputMint) && itemAmmPool.mintB.equals(outputMint)) || (itemAmmPool.mintA.equals(outputMint) && itemAmmPool.mintB.equals(inputMint)) ) { directPath.push(itemAmmPool); needSimulate[itemAmmPool.id.toBase58()] = itemAmmPool; addLiquidityPools.push(itemAmmPool); } if (itemAmmPool.mintA.equals(inputMint)) { if (routePathDict[itemAmmPool.mintB.toBase58()] === undefined) routePathDict[itemAmmPool.mintB.toBase58()] = { mintProgram: TOKEN_PROGRAM_ID, in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[itemAmmPool.mintB.toBase58()].in.push(itemAmmPool); } if (itemAmmPool.mintB.equals(inputMint)) { if (routePathDict[itemAmmPool.mintA.toBase58()] === undefined) routePathDict[itemAmmPool.mintA.toBase58()] = { mintProgram: TOKEN_PROGRAM_ID, in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[itemAmmPool.mintA.toBase58()].in.push(itemAmmPool); } if (itemAmmPool.mintA.equals(outputMint)) { if (routePathDict[itemAmmPool.mintB.toBase58()] === undefined) routePathDict[itemAmmPool.mintB.toBase58()] = { mintProgram: TOKEN_PROGRAM_ID, in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[itemAmmPool.mintB.toBase58()].out.push(itemAmmPool); } if (itemAmmPool.mintB.equals(outputMint)) { if (routePathDict[itemAmmPool.mintA.toBase58()] === undefined) routePathDict[itemAmmPool.mintA.toBase58()] = { mintProgram: TOKEN_PROGRAM_ID, in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[itemAmmPool.mintA.toBase58()].out.push(itemAmmPool); } } for (const itemCpmmPool of cpmmPools) { if ( (itemCpmmPool.mintA.equals(inputMint) && itemCpmmPool.mintB.equals(outputMint)) || (itemCpmmPool.mintA.equals(outputMint) && itemCpmmPool.mintB.equals(inputMint)) ) { directPath.push(itemCpmmPool); cpmmPoolList[itemCpmmPool.id.toBase58()] = itemCpmmPool; } if (itemCpmmPool.mintA.equals(inputMint)) { if (routePathDict[itemCpmmPool.mintB.toBase58()] === undefined) routePathDict[itemCpmmPool.mintB.toBase58()] = { mintProgram: TOKEN_PROGRAM_ID, in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[itemCpmmPool.mintB.toBase58()].in.push(itemCpmmPool); } if (itemCpmmPool.mintB.equals(inputMint)) { if (routePathDict[itemCpmmPool.mintA.toBase58()] === undefined) routePathDict[itemCpmmPool.mintA.toBase58()] = { mintProgram: TOKEN_PROGRAM_ID, in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[itemCpmmPool.mintA.toBase58()].in.push(itemCpmmPool); } if (itemCpmmPool.mintA.equals(outputMint)) { if (routePathDict[itemCpmmPool.mintB.toBase58()] === undefined) routePathDict[itemCpmmPool.mintB.toBase58()] = { mintProgram: TOKEN_PROGRAM_ID, in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[itemCpmmPool.mintB.toBase58()].out.push(itemCpmmPool); } if (itemCpmmPool.mintB.equals(outputMint)) { if (routePathDict[itemCpmmPool.mintA.toBase58()] === undefined) routePathDict[itemCpmmPool.mintA.toBase58()] = { mintProgram: TOKEN_PROGRAM_ID, in: [], out: [], mDecimals: 0, // to fetch later }; routePathDict[itemCpmmPool.mintA.toBase58()].out.push(itemCpmmPool); } } for (const t of Object.keys(routePathDict)) { if ( routePathDict[t].in.length === 1 && routePathDict[t].out.length === 1 && routePathDict[t].in[0].id.equals(routePathDict[t].out[0].id) ) { delete routePathD