Prerequisites

Before you begin, ensure you have:
  • Node.js installed
  • A Solana wallet with some mainnet SOL
  • Your Cleopetra API Key (contact our team to get one)

Installation

Install the required dependencies:
npm install @solana/web3.js bs58

Setup

First, set up your environment and API configuration:
import {
  Connection,
  Keypair,
  VersionedTransaction,
  LAMPORTS_PER_SOL,
} from "@solana/web3.js";
import base58 from "bs58";

const API_KEY = "your-cleo-api-key";
const PRIVATE_KEY = "your-private-key-here"; // string
const API_URL = "https://api.cleopetra.fun";
const RPC_URL = "your-solana-rpc-url";

const connection = new Connection(RPC_URL);
const keypair = Keypair.fromSecretKey(base58.decode(PRIVATE_KEY));

const sendRequest = async (route: string, params: any) => {
  const response = await fetch(`${API_URL}${route}`, {
    method: route.includes('positions') || route.includes('pools') ? 'GET' : 'POST',
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: route.includes('positions') || route.includes('pools') 
      ? undefined 
      : JSON.stringify(params),
  });

  if (!response.ok) {
    throw new Error(`API Error: ${response.status}`);
  }

  return await response.json();
};

Transaction Sending Helper

Add this helper function to handle transaction signing and sending:
This snippet adds position keypair to all txns in the array, which is unnecessary. Please refer to the advanced example script for optimizations.
Use transaction sending services like Jito in production.
const sendTransaction = async (
  base64Txns: string[],
  positions: string[] = [],
  positionNftMintKp?: string
) => {
  const signatures: string[] = [];
  
  for (const [i, base64Txn] of base64Txns.entries()) {
    try {
      const { blockhash, lastValidBlockHeight } = 
        await connection.getLatestBlockhash();
      
      const transaction = VersionedTransaction.deserialize(
        Buffer.from(base64Txn, "base64")
      );
      
      // Update txn with latest blockhash
      transaction.message.recentBlockhash = blockhash;
      
      const signers: Keypair[] = [keypair];
      
      // Add position keypair if provided
      if (positions && positions.length > 0) {
        for (const position of positions) {
          if (position) {
            const positionKp = Keypair.fromSecretKey(
              Buffer.from(position, "base64")
            );
            signers.push(positionKp);
          }
        }
      }
      
      // Add NFT mint keypair for DAMMv2
      if (positionNftMintKp) {
        const nftKp = Keypair.fromSecretKey(
          Buffer.from(positionNftMintKp, "base64")
        );
        signers.push(nftKp);
      }
      
      // Sign and send
      transaction.sign(signers);
      const signature = await connection.sendTransaction(transaction);
      
      await connection.confirmTransaction({
        signature,
        blockhash,
        lastValidBlockHeight,
      }, "confirmed");
      
      console.log(`Transaction ${i + 1} confirmed:`, signature);
      signatures.push(signature);
      
    } catch (error) {
      console.error(`Failed to send transaction ${i + 1}:`, error);
    }
  }
  
  return signatures;
};

Step 1: Discover Pools

Find the best liquidity pools for a specific token:
const discoverPools = async () => {
  const tokenMint = "9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump"; // Fartcoin
  
  const queryParams = new URLSearchParams({
    token: tokenMint,
    programs: "dlmm,dammv2",
    order_by: "desc",
    sort_by: "volume"
  });
  
  const pools = await sendRequest(`/pools/token?${queryParams}`, null);
  console.log("Available pools:", pools.slice(0, 3));
  
  // Choose the top DLMM pool
  const topDlmmPool = pools.find(pool => pool.type === "Dlmm");
  if (!topDlmmPool) {
    throw new Error("No DLMM pool found for this token");
  }
  
  console.log("Selected pool:", {
    address: topDlmmPool.address,
    tvl: topDlmmPool.tvl,
    volume24h: topDlmmPool.volume_24h,
    apr: topDlmmPool.apr
  });
  
  return topDlmmPool;
};

Step 2: Create DLMM Position

Create a liquidity position in the selected pool:
const createPosition = async (pool: any) => {
  // Calculate price range (5% above and below current price)
  const currentPrice = pool.price;
  const minPrice = currentPrice * 0.95;
  const maxPrice = currentPrice * 1.05;
  
  const params = {
    user: keypair.publicKey.toString(),
    mode: "zap", // Automatically converts SOL to required tokens
    pool: pool.address,
    strategy: "spot",
    amount_sol: (1 * LAMPORTS_PER_SOL).toString(), // 1 SOL
    min_price: minPrice,
    max_price: maxPrice,
    slippage: 500, // 5% slippage
  };
  
  console.log("Creating position with params:", params);
  
  const result = await sendRequest("/dlmm/initialize", params);
  console.log("Position creation result:", result);
  
  if (result?.transactions) {
    const signatures = await sendTransaction(
      result.transactions,
      result.positions
    );
    console.log("Position created successfully!");
    return signatures[0];
  }
  
  throw new Error("Failed to create position");
};

Step 3: Fetch User Positions

Retrieve all positions for your wallet:
const fetchPositions = async () => {
  const queryParams = new URLSearchParams({
    user: keypair.publicKey.toString(),
    programs: "dlmm,dammv2",
    limit: "10",
    page: "1"
  });
  
  const result = await sendRequest(`/positions?${queryParams}`, null);
  console.log(`Found ${result.total} positions:`, result.positions);
  
  if (result.positions.length === 0) {
    throw new Error("No positions found for this wallet");
  }
  
  return result.positions;
};

Step 4: Claim LP Fees

Claim accumulated fees from your first position:
const claimFees = async (positions: any[]) => {
  const firstPosition = positions[0];
  console.log("Claiming fees for position:", firstPosition.position_address);
  console.log("Unclaimed fees (USD):", firstPosition.total_unclaimed_fees_usd);
  
  if (firstPosition.total_unclaimed_fees_usd === 0) {
    console.log("No fees to claim for this position");
    return;
  }
  
  const params = {
    user: keypair.publicKey.toString(),
    mode: "zap", // Convert claimed tokens to SOL if needed
    position: firstPosition.position_address,
    slippage: 500,
  };
  
  const result = await sendRequest("/dlmm/claim", params);
  console.log("Claim result:", result);
  
  if (result?.transactions) {
    const signatures = await sendTransaction(result.transactions);
    console.log("Fees claimed successfully!");
    return signatures;
  }
  
  throw new Error("Failed to claim fees");
};

Complete Workflow

Put it all together in a complete example:
const main = async () => {
  try {
    console.log("🔍 Step 1: Discovering pools...");
    const selectedPool = await discoverPools();
    
    console.log("\n💰 Step 2: Creating DLMM position...");
    await createPosition(selectedPool);
    
    // Wait a moment for the position to be indexed
    console.log("\n⏳ Waiting for position indexing...");
    await new Promise(resolve => setTimeout(resolve, 5000));
    
    console.log("\n📊 Step 3: Fetching positions...");
    const positions = await fetchPositions();
    
    console.log("\n🎯 Step 4: Claiming LP fees...");
    await claimFees(positions);
    
    console.log("\n✅ Workflow completed successfully!");
    
  } catch (error) {
    console.error("❌ Workflow failed:", error);
  }
};

main();
Full example script you can refer.

Next Steps