Dev.to AI 🤖 Ai 👁 0 📖 4 min read

Catching Fake Crypto Volume with a Graph: Building a Wash-Trade Investigator on FalkorDB

Built for Graph Hacks: Context for AI Agents (WeMakeDevs x FalkorDB), Track 1: Agents That Act on Connected Data. Code: https://github.com/bibliophile17/Wash-Trade-Investigator-graph-agent-on-FalkorDB The prob

Built for Graph Hacks: Context for AI Agents (WeMakeDevs x FalkorDB), Track 1: Agents That Act on Connected Data.

Code: https://github.com/bibliophile17/Wash-Trade-Investigator-graph-agent-on-FalkorDB

The problem: fake volume that looks real

Wash trading is when a group of wallets trade a token back and forth among themselves so it looks popular. High volume attracts real buyers, and the people behind the ring cash out.

The hard part is that every single trade looks normal. A price, an amount, two wallets. If you look at trades as rows in a table, nothing stands out.

The fraud is in the shape: money moving A -> B -> C -> A, again and again, between wallets that were all funded by the same source. That is a pattern of relationships, and that is what graphs are good at.

What I built

Wash-Trade Investigator is an agent that investigates a token and does three things:

  1. Decides whether the token's volume is real, using graph queries as its tools.
  2. Acts: flags the suspicious wallets and writes a Case node linked to them in the graph.
  3. Explains: draws the exact subgraph of wallets, trades and funders behind the decision.

The graph model is small:

(:Wallet)-[:TRADED {token, amount, ts}]->(:Wallet)
(:Wallet)-[:FUNDED_BY]->(:Funder)
(:Case)-[:FLAGGED]->(:Wallet)

My seed script creates 300 normal wallets with random trades, plus two planted wash-trading rings. The ring membership is never stored in the graph, so the agent has to discover it.

How the agent investigates

Each tool is one Cypher query. First it looks for wallets trading in circles:

MATCH (a:Wallet)-[x:TRADED]->(b:Wallet)-[y:TRADED]->(a)
WHERE x.token = $t AND y.token = $t AND a.id < b.id
RETURN a.id, b.id, count(x) AS legs

A single round trip can happen by chance, so I only treat pairs with repeated round trips as signals, and I also look for three-wallet cycles. Wallets connected by these cycles are merged into clusters.

For each cluster the agent then asks:

  • How much of the token's volume stays inside the cluster? In the ring it found, 97.5%.
  • Are the trade sizes suspiciously uniform? Trade sizes varied by only 5.6%.
  • Do the wallets share a funder? All six were funded by the same wallet.
  • Who is the hub? FalkorDB's PageRank picked one wallet out of the ring.

If the evidence adds up, it flags the cluster. In my test it flagged exactly the six planted wallets. It also found a second cluster of three wallets with circular trades, but only 50% internal volume, widely varying trade sizes and no shared funder, so it correctly left them alone. That second case matters: an agent that flags everything isn't useful.

Two modes

The agent runs in two modes with the same tools. In LLM mode (with an API key) a model chooses which tools to call. Without a key, an offline mode runs a fixed playbook with a transparent scoring rule. I made the offline mode so the project works for anyone who clones it, with nothing but a FalkorDB instance.

What I learned

Graphs make the question easy to ask. "Find wallets that trade in circles and share a funder" is a few lines of Cypher. In SQL it would be a pile of self-joins.

Let the graph do the evidence. Because the agent works from query results, every decision comes with the nodes and edges behind it. The explanation is not an extra step, it is the output of the investigation.

Write the action back to the graph. Creating a Case node linked to the flagged wallets means the decision becomes part of the data, ready for later investigations to build on.

Setup friction is real. I don't have Docker on my laptop, so FalkorDB Cloud's free tier saved me. I also lost time because I pasted the host and port together and got a confusing host:port:port connection error. After that I made my connection code strip a port from the host. Small lesson: validate your own config inputs.

Build with ground truth. Planting the rings myself meant I could check the agent's answer instead of guessing whether it worked.

What's next

  • Real chain data instead of synthetic trades, and timing-based signals
  • Tuned scoring thresholds
  • Exposing the same graph tools through an MCP server so any agent client can use them
  • Streaming the agent's tool calls live in the UI

Thanks to WeMakeDevs and FalkorDB for the hackathon. If you want to try it, the README has setup steps for both FalkorDB Cloud and Docker.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.