Skip to main content

What You’ll Build

A script that checks whether a token’s liquidity pool is safe by analyzing LP holder distribution — burned, locked in known protocols (Unicrypt, PinkLock, Team Finance, Mudra), held by contracts, or sitting unlocked in regular wallets.

Why It Matters

The #1 rug pull vector is unlocked liquidity. A developer who holds LP tokens can remove all liquidity at any time, crashing the token price to zero. The liquidityAnalysis field gives you a per-pool breakdown of exactly who holds the LP tokens and whether they’re safe.

Quick Start

curl -s "https://api.mobula.io/api/2/token/security?blockchain=ethereum&address=0x6982508145454ce325ddbe47a25d4ec3d2311933" \
  -H "Authorization: YOUR_API_KEY" | jq '.data.liquidityAnalysis'

Understanding the Response

The liquidityAnalysis array returns the top 3 pools by 24h volume. Each pool includes a full holder breakdown:
{
  "liquidityAnalysis": [
    {
      "poolAddress": "0xa43fe16908251ee70ef74718545e4fe6c5ccec8f",
      "poolType": "uniswap-v2",
      "burnedPercentage": 36.4,
      "lockedPercentage": 0,
      "contractPercentage": 0.8,
      "unlockedPercentage": 62.8,
      "topHolders": [
        { "address": "0x371d...", "percentage": 31.8, "type": "unlocked", "protocol": null },
        { "address": "0x0000...0000", "percentage": 20.6, "type": "burned", "protocol": null },
        { "address": "0x663a...", "percentage": 16.2, "type": "locked", "protocol": "Unicrypt" }
      ]
    }
  ]
}

Holder Types

TypeWhat It MeansRisk Level
burnedLP tokens sent to a dead address. Liquidity is permanently locked.Safe
lockedLP tokens held by a locker protocol (Unicrypt, PinkLock, etc.). Locked for a defined period.Safe (while lock active)
contractLP tokens in an unrecognized contract. Could be a custom lock, a multisig, or something else.Investigate
unlockedLP tokens in a regular wallet. Can be removed at any time.Risk

Cookbook: Rug Pull Risk Scorer

This TypeScript function takes a token address and returns a risk assessment:
const API_KEY = "YOUR_API_KEY";

interface LiquidityRisk {
  riskLevel: "safe" | "moderate" | "high" | "critical";
  burnedPct: number;
  lockedPct: number;
  unlockedPct: number;
  topUnlockedHolder: { address: string; percentage: number } | null;
  pools: number;
  details: string;
}

async function assessLiquidityRisk(
  address: string,
  blockchain: string
): Promise<LiquidityRisk> {
  const res = await fetch(
    `https://api.mobula.io/api/2/token/security?address=${address}&blockchain=${blockchain}`,
    { headers: { Authorization: API_KEY } }
  );
  const { data } = await res.json();
  const pools = data.liquidityAnalysis;

  if (!pools || pools.length === 0) {
    return {
      riskLevel: "critical",
      burnedPct: 0,
      lockedPct: 0,
      unlockedPct: 100,
      topUnlockedHolder: null,
      pools: 0,
      details: "No liquidity analysis available",
    };
  }

  // Aggregate across all pools (weighted by position in array = volume rank)
  let totalBurned = 0, totalLocked = 0, totalUnlocked = 0;
  let topUnlocked: { address: string; percentage: number } | null = null;

  for (const pool of pools) {
    totalBurned += pool.burnedPercentage;
    totalLocked += pool.lockedPercentage;
    totalUnlocked += pool.unlockedPercentage;

    for (const holder of pool.topHolders) {
      if (holder.type === "unlocked") {
        if (!topUnlocked || holder.percentage > topUnlocked.percentage) {
          topUnlocked = { address: holder.address, percentage: holder.percentage };
        }
      }
    }
  }

  // Average across pools
  const n = pools.length;
  const burned = totalBurned / n;
  const locked = totalLocked / n;
  const unlocked = totalUnlocked / n;
  const safePct = burned + locked;

  let riskLevel: LiquidityRisk["riskLevel"];
  let details: string;

  if (safePct >= 90) {
    riskLevel = "safe";
    details = `${burned.toFixed(1)}% burned + ${locked.toFixed(1)}% locked across ${n} pool(s)`;
  } else if (safePct >= 60) {
    riskLevel = "moderate";
    details = `${unlocked.toFixed(1)}% unlocked — some rug pull risk remains`;
  } else if (safePct >= 30) {
    riskLevel = "high";
    details = `Only ${safePct.toFixed(1)}% of LP is burned/locked — significant rug risk`;
  } else {
    riskLevel = "critical";
    details = `${unlocked.toFixed(1)}% of LP is unlocked — high probability of rug pull`;
  }

  return { riskLevel, burnedPct: burned, lockedPct: locked, unlockedPct: unlocked, topUnlockedHolder: topUnlocked, pools: n, details };
}

// Usage
const risk = await assessLiquidityRisk("0x6982508145454ce325ddbe47a25d4ec3d2311933", "ethereum");
console.log(`Risk: ${risk.riskLevel}${risk.details}`);

Supported Protocols

LP Locker Detection

When LP tokens are held by a recognized locker contract, the protocol field identifies it:
ProtocolChainsDescription
UnicryptEthereum, BSC, Arbitrum, BaseLargest LP locker, time-locked vaults
Team FinanceEthereum, BSCTeam token and LP locking
PinkLockEthereum, BSC, Arbitrum, BasePinkSale ecosystem locker
Mudra LockerBSCBSC-focused LP locker

Supported DEX Pools

Liquidity analysis works across all major DEX types: EVM:
  • Uniswap V2/V3/V4, PancakeSwap, Camelot, Balancer, Curve, Solidly/Aerodrome, Fluid
Solana:
  • PumpSwap, Raydium (AMM V4, CPMM), Meteora (Dynamic AMM, DBC, DLMM), Orca Whirlpool

Combining with Other Security Signals

For a complete security assessment, combine liquidityAnalysis with other fields from the same endpoint:
const { data } = await res.json();

const redFlags = [];

// Liquidity risk
if (!data.liquidityAnalysis || data.liquidityAnalysis.length === 0) {
  redFlags.push("No LP analysis available");
} else if (data.liquidityAnalysis[0].unlockedPercentage > 80) {
  redFlags.push(`${data.liquidityAnalysis[0].unlockedPercentage}% LP unlocked`);
}

// Contract risks
if (data.isHoneypot) redFlags.push("Honeypot detected");
if (data.balanceMutable) redFlags.push("Balance can be manipulated");
if (data.isMintable) redFlags.push("Supply can be minted");
if (!data.renounced) redFlags.push("Ownership not renounced");
if (data.sellFeePercentage > 10) redFlags.push(`High sell fee: ${data.sellFeePercentage}%`);

// Holder concentration
if (data.top10HoldingsPercentage > 50) {
  redFlags.push(`Top 10 holders control ${data.top10HoldingsPercentage}%`);
}

console.log(redFlags.length === 0 ? "No red flags" : `Red flags: ${redFlags.join(", ")}`);