Skip to main content

Before we begin

API Workflow

Understand how to integrate LP features into your product using our APIs by following this workflow diagram.

Installation

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

Setup

First, set up your environment and API configuration:
Reach out to our team on Discord to receive your API Key
import { Connection, Keypair, VersionedTransaction } 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://public-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:
const getRequiredSigners = (txn: VersionedTransaction): string[] => {
  return txn.message.staticAccountKeys
    .filter((_, idx) => txn.message.isAccountSigner(idx))
    .map((pk) => pk.toBase58());
};

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")
      );

      transaction.message.recentBlockhash = blockhash;

      const requiredSigners = getRequiredSigners(transaction);
      const signers: Keypair[] = [];

      const addSignerIfNeeded = (kp: Keypair) => {
        const key = kp.publicKey.toBase58();
        if (
          requiredSigners.includes(key) &&
          !signers.some((s) => s.publicKey.equals(kp.publicKey))
        ) {
          signers.push(kp);
        }
      };

      addSignerIfNeeded(keypair);

      if (positions) {
        for (const [idx, position] of positions.entries()) {
          if (!position) continue;
          try {
            const positionKp = Keypair.fromSecretKey(
              Buffer.from(position, "base64")
            );
            addSignerIfNeeded(positionKp);
          } catch (err) {
            console.warn(`Failed to parse position keypair ${idx}:`, err);
          }
        }
      }

      if (positionNftMintKp) {
        try {
          const nftKp = Keypair.fromSecretKey(
            Buffer.from(positionNftMintKp, "base64")
          );
          addSignerIfNeeded(nftKp);
        } catch (err) {
          console.warn("Failed to parse NFT mint keypair:", err);
        }
      }

      const signerKeys = signers.map((s) => s.publicKey.toBase58());
      const missingSigners = requiredSigners.filter(
        (rs) => !signerKeys.includes(rs)
      );

      if (missingSigners.length > 0) {
        console.error(
          `Missing required signers for transaction ${i + 1}:`,
          missingSigners
        );
        continue;
      }

      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;
};
For web apps using the Solana wallet adapter, it’s important to sign all transactions at once using the signAllTransactions hook, and then send them on-chain one by one in sequence.
Use transaction sending services like Jito in production.

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.89, // 1.89 SOL
    min_price: minPrice.toString(),
    max_price: maxPrice.toString(),
  };

  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();

Next Steps

API Demo Scaffold

Explore a ready-to-use TypeScript scaffold with Cleopetra API examples and workflow integration.