import { Keypair, PublicKey, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; import { SolanaAgentKit } from "../agent"; import { Wallet } from "@coral-xyz/anchor"; import { Decimal } from "decimal.js"; import { ORCA_WHIRLPOOL_PROGRAM_ID, WhirlpoolContext, PriceMath, PoolUtil, buildWhirlpoolClient, } from "@orca-so/whirlpools-sdk"; import { sendTx } from "../utils/send_tx"; import { FEE_TIERS } from "./orca_create_single_sided_liquidity_pool"; /** * # Creates a CLMM Pool (Concentrated Liquidity Market Maker Pool). * * This function initializes a new Whirlpool (CLMM Pool) on Orca. It only sets up the pool and does not seed it with liquidity. * * ## Example Usage: * Suppose you want to create a CLMM pool with two tokens, SHARK and USDC, and set the initial price of SHARK to 0.001 USDC. * You would call this function with `mintA` as SHARK's mint address and `mintB` as USDC's mint address. The pool is created * with the specified fee tier and tick spacing associated with that fee tier. * * ### Note for Experts: * The Whirlpool program determines the token mint order, which might not match your expectation. This function * adjusts the input order as needed and inverts the initial price accordingly. * * @param agent - The `SolanaAgentKit` instance representing the wallet and connection details. * @param mintDeploy - The mint of the token you want to deploy (e.g., SHARK). * @param mintPair - The mint of the token you want to pair the deployed mint with (e.g., USDC). * @param initialPrice - The initial price of `mintDeploy` in terms of `mintPair`. * @param feeTier - The fee tier bps for the pool, determining tick spacing and fee collection rates. * * @returns A promise that resolves to a transaction ID (`string`) of the transaction creating the pool. * * @throws Will throw an error if: * - Mint accounts for the tokens cannot be fetched. * - The network is unsupported. * * @remarks * This function only initializes the CLMM pool and does not add liquidity. For adding liquidity, you can use * a separate function after the pool is successfully created. * ``` */ export async function orcaCreateCLMM( agent: SolanaAgentKit, mintDeploy: PublicKey, mintPair: PublicKey, initialPrice: Decimal, feeTier: keyof typeof FEE_TIERS, ): Promise { try { let whirlpoolsConfigAddress: PublicKey; if (agent.connection.rpcEndpoint.includes('mainnet')) { whirlpoolsConfigAddress = new PublicKey('2LecshUwdy9xi7meFgHtFJQNSKk4KdTrcpvaB56dP2NQ'); } else if (agent.connection.rpcEndpoint.includes('devnet')) { whirlpoolsConfigAddress = new PublicKey('FcrweFY1G9HJAHG5inkGB6pKg1HZ6x9UC2WioAfWrGkR'); } else { throw new Error('Unsupported network'); } const wallet = new Wallet(agent.wallet); const ctx = WhirlpoolContext.from( agent.connection, wallet, ORCA_WHIRLPOOL_PROGRAM_ID, ); const fetcher = ctx.fetcher; const client = buildWhirlpoolClient(ctx) const correctTokenOrder = PoolUtil.orderMints( mintDeploy, mintPair, ).map((addr) => addr.toString()); const isCorrectMintOrder = correctTokenOrder[0] === mintDeploy.toString(); let mintA; let mintB; if (!isCorrectMintOrder) { [mintA, mintB] = [mintPair, mintDeploy]; initialPrice = new Decimal(1 / initialPrice.toNumber()); } else { [mintA, mintB] = [mintDeploy, mintPair]; } const mintAAccount = await fetcher.getMintInfo(mintA); const mintBAccount = await fetcher.getMintInfo(mintB); if (mintAAccount === null || mintBAccount === null) { throw Error("Mint account not found"); } const tickSpacing = FEE_TIERS[feeTier]; const initialTick = PriceMath.priceToInitializableTickIndex(initialPrice, mintAAccount.decimals, mintBAccount.decimals, tickSpacing) const { poolKey, tx: txBuilder } = await client.createPool( whirlpoolsConfigAddress, mintA, mintB, tickSpacing, initialTick, wallet.publicKey, ) const txPayload = await txBuilder.build(); const txPayloadDecompiled = TransactionMessage.decompile((txPayload.transaction as VersionedTransaction).message); const instructions = txPayloadDecompiled.instructions; const txId = await sendTx( agent, instructions, txPayload.signers as Keypair[] ); return JSON.stringify({ transactionId: txId, whirlpoolAddress: poolKey.toString(), }); } catch (error) { console.log(error) throw new Error(`${error}`); } }