Before we begin

Understand how to integrate LP features into your product using our APIs by following this workflow diagram.
Installation
Install the required dependencies:Copy
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
Copy
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:Copy
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:Copy
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:Copy
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:Copy
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:Copy
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:Copy
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.

