DEV Community

boba bobo
boba bobo

Posted on

I Built a Fake Market Detector Using DEX Trade Fees

Last month I almost aped into a Solana token showing $2M in daily volume. Everything looked legit active chart, thousands of transactions, growing holder count.

Then I looked at the fees. The token had generated $12 in total platform fees on $2M of volume. That's a 0.0006% fee ratio. On a real market, that number should be somewhere between 0.5% and 2%.

It was a wash traded token. The "volume" was bots trading back and forth with themselves. I would have lost everything.

That's when I started building a market legitimacy scanner powered by fee data. Here's how it works and how you can build one too.

Why Fees Can't Be Faked

Every DEX trade has three cost components that most platforms hide from you:

Component What it is Typical cost (Solana)
Gas Fee Network cost to validators ~$0.35
Platform Fee Aggregator cut (Axiom, GMGN, etc.) ~1% of trade
MEV Fee Priority tips, Jito bundles ~$2-5

Here's the key insight: on a $2,500 trade, the platform fee ($25) is 90% of your total cost but most interfaces only show you the gas fee.

More importantly: real traders pay real fees. Wash traders don't. They control the entire flow, often trading through direct DEX contracts to avoid platform fees entirely. This asymmetry is your detection signal.

According to Chainalysis's 2025 report, over 42% of tokens launched in 2024 were listed on a DEX, and wash trading remains one of the most prevalent forms of manipulation. Solidus Labs found that self-trading on DEX pools is far more common than expected.

The Detection Logic

The math is simple:

Fee-to-Volume Ratio = (Fees Paid 24h / Volume 24h) × 100
Enter fullscreen mode Exit fullscreen mode

Here's what the ratio tells you:

Ratio Signal
0.5% - 2.5% ✅ Healthy, organic trading
0.1% - 0.5% ⚠️ Investigate further
< 0.1% with high volume 🚨 Very likely wash traded
0% with any volume 🚨 Probable honeypot or scam
> 5% ⚠️ Unusual fee structure

A token with $500K daily volume but only $50 in fees? That's a 0.01% ratio. Something is very wrong.

Fetching Fee Data with Mobula API

Most crypto APIs only give you volume and price. Mobula exposes the full fee breakdown — gas, platform, and MEV — per trade and aggregated per token.

Get token-level fee stats

curl -X GET "https://api.mobula.io/api/2/token/details?blockchain=solana&address=TOKEN_ADDRESS" \
  -H "Authorization: YOUR_API_KEY"
Enter fullscreen mode Exit fullscreen mode

The response includes fee metrics across multiple timeframes:

{
  "data": {
    "symbol": "EXAMPLE",
    "totalFeesPaidUSD": 125000.50,
    "feesPaid24hUSD": 4450.00,
    "feesPaid1hUSD": 185.00,
    "feesPaid5minUSD": 15.50,
    "volume24hUSD": 890000.00
  }
}
Enter fullscreen mode Exit fullscreen mode

Get individual trade fee breakdown

curl -X GET "https://api.mobula.io/api/2/token/trades?blockchain=solana&address=TOKEN_ADDRESS&limit=50" \
  -H "Authorization: YOUR_API_KEY"
Enter fullscreen mode Exit fullscreen mode

Each trade returns:

  • totalFeesUSD — sum of all fee components
  • gasFeesUSD — network transaction cost
  • platformFeesUSD — aggregator/frontend fee
  • mevFeesUSD — priority and MEV-related cost

Building the Scanner

Here's a TypeScript scanner that analyzes any token and flags suspicious fee patterns:

import { MobulaClient } from "@mobula_labs/sdk";

interface FeeAnalysis {
  symbol: string;
  volume24h: number;
  fees24h: number;
  feeRatio: number;
  verdict: "ORGANIC" | "SUSPICIOUS" | "WARNING";
  reason: string;
}

async function scanToken(
  blockchain: string,
  address: string
): Promise<FeeAnalysis> {
  const client = new MobulaClient({ apiKey: process.env.MOBULA_API_KEY! });

  const details = await client.fetchTokenDetails({
    blockchain,
    address,
  });

  const volume = details.volume24hUSD ?? 0;
  const fees = details.feesPaid24hUSD ?? 0;
  const symbol = details.symbol ?? "UNKNOWN";
  const ratio = volume > 0 ? (fees / volume) * 100 : 0;

  // Zero fees + active volume = honeypot signal
  if (fees === 0 && volume > 10_000) {
    return {
      symbol, volume24h: volume, fees24h: fees,
      feeRatio: 0, verdict: "WARNING",
      reason: "Zero fees with active volume — possible honeypot",
    };
  }

  // Very low ratio + high volume = wash trading signal
  if (ratio < 0.1 && volume > 100_000) {
    return {
      symbol, volume24h: volume, fees24h: fees,
      feeRatio: ratio, verdict: "SUSPICIOUS",
      reason: `Fee ratio ${ratio.toFixed(4)}% is far below expected 0.5-2%`,
    };
  }

  // Abnormally high fees = investigate
  if (ratio > 5) {
    return {
      symbol, volume24h: volume, fees24h: fees,
      feeRatio: ratio, verdict: "WARNING",
      reason: `Fee ratio ${ratio.toFixed(2)}% is unusually high`,
    };
  }

  return {
    symbol, volume24h: volume, fees24h: fees,
    feeRatio: ratio, verdict: "ORGANIC",
    reason: "Fee distribution looks normal",
  };
}

// Scan multiple tokens
async function main() {
  const tokens = [
    "9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump",
    "8J69rbLTzWWgUJziFY8jeu5tDQEPBwUz4pKBMr5rpump",
  ];

  for (const addr of tokens) {
    const result = await scanToken("solana", addr);
    const icon =
      result.verdict === "ORGANIC" ? "" :
      result.verdict === "SUSPICIOUS" ? "⚠️" : "🚨";

    console.log(`\n${icon} ${result.symbol} [${result.verdict}]`);
    console.log(`   Volume 24h: $${result.volume24h.toLocaleString()}`);
    console.log(`   Fees 24h:   $${result.fees24h.toLocaleString()}`);
    console.log(`   Fee ratio:  ${result.feeRatio.toFixed(4)}%`);
    console.log(`   → ${result.reason}`);
  }
}

main();
Enter fullscreen mode Exit fullscreen mode

Output looks like this:

✅ FARTCOIN [ORGANIC]
   Volume 24h: $12,450,000
   Fees 24h:   $89,200
   Fee ratio:  0.7166%
   → Fee distribution looks normal

🚨 SCAMTOKEN [WARNING]
   Volume 24h: $2,100,000
   Fees 24h:   $12
   Fee ratio:  0.0006%
   → Zero fees with active volume — possible honeypot
Enter fullscreen mode Exit fullscreen mode

Real-Time Monitoring with WebSocket

For live alerting, connect to Mobula's trade stream:

const ws = new WebSocket("wss://general-api-v2.mobula.io");

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: "fast-trades",
    authorization: process.env.MOBULA_API_KEY,
    payload: {
      tokens: [
        { address: "TOKEN_ADDRESS", chainId: "solana:solana" }
      ],
    },
  }));
};

ws.onmessage = (event) => {
  const trade = JSON.parse(event.data);
  const feePercent = (trade.totalFeesUSD / trade.baseTokenAmountUSD) * 100;

  // Alert on suspicious zero-fee large trades
  if (feePercent < 0.01 && trade.baseTokenAmountUSD > 1000) {
    console.log(`🚨 SUSPICIOUS: $${trade.baseTokenAmountUSD} trade with $${trade.totalFeesUSD} fees`);
  }
};
Enter fullscreen mode Exit fullscreen mode

Why This Matters

Fee data flips the script on how we evaluate tokens:

Metric Can be faked? Reliability
Volume ✅ Yes (wash trading) Low
Transaction count ✅ Yes (spam txs) Low
Fees paid ❌ No (costs real money) High
Unique traders Partially (sybil) Medium

Tokens with rapidly increasing feesPaid1hUSD indicate genuine interest from real traders willing to pay premium costs. That's a signal you can't manufacture cheaply.

Top comments (0)