Opens your running SynapCores (Fraud ring detection in under 50ms will be staged for a preview — nothing runs until you click Run). No instance yet? Install free in ~30s.
Share
⚠️ Known issue (2026-10-16): on the current shipped engine builds (v2.1.3-ce and v2.2.0-ce) this recipe returns INCORRECT results and should not be relied on yet. Closed-path cycle matching is not correct: a repeated node variable (e.g. (x)…->(x)) is not bound to the same node, so the query returns open chains instead of the real ring and drops the true cycle. Separately, MERGE on a relationship currently ignores its properties, so distinct transfers can collapse into one edge and a ring hop is lost. The detected ring and its totals are therefore wrong. A fix is tracked with engineering; the recipe will be re-certified once it lands.
Try it in your browser: the SynapCores Playground runs a Cypher ring walk over a live identity graph. No install.
Objective
Fraud rings move money in circles — A pays B pays C pays A — to make stolen funds look like
legitimate revenue. The pattern is invisible to row-level rules but obvious as a cycle in the
transaction graph. This recipe builds a small money-flow graph with one embedded ring and finds
it with a single MATCH on a closed path.
// Closed cycles of 3 to 4 hops with every hop above $50k:
// walk 2-3 hops out, then require a transfer from the last account back to the start.
MATCH p = (start:Account)-[t:TRANSFER*2..3]->(last:Account)
MATCH (last)-[closing:TRANSFER]->(back:Account)
WHERE back.id = start.id
AND ALL(r IN t WHERE r.amount > 50000)
AND closing.amount > 50000
AND ALL(n IN nodes(p) WHERE start.id <= n.id) // report each ring once
RETURN [n IN nodes(p) | n.id] AS ring_accounts,
[r IN t | r.amount] AS path_hops,
closing.amount AS closing_hop,
reduce(s = 0, r IN t | s + r.amount) + closing.amount AS total_moved;
Expected: one ring — ACC-003 → ACC-004 → ACC-005 → ACC-006 and back to ACC-003, hops
78,000, 76,500 and 74,000 plus the closing 71,500, 300,000 moved in total. Nothing else
qualifies: the other loop in the data (Sarah Chen → Acme → Eli Tanaka → Sarah Chen) only moves
a few hundred dollars per hop.
What's happening
The variable-length pattern *2..3 walks out from each account; the second MATCH requires a
transfer from the last account back to the start (back.id = start.id), which closes the cycle.
ALL(n IN nodes(p) WHERE start.id <= n.id) keeps one copy of each ring instead of one per
starting account.
ALL(r IN t WHERE r.amount > 50000) filters out rings of trivial amounts at traversal time.
reduce() totals the laundered amount inline, no second query.
In SQL this needs a 4-way self-join with equality on the start/end account plus AML thresholds —
N^4 cost on the transaction table. On the graph it's local: only edges from start are visited.
Tight latency (<50ms on millions of edges) means rules can run on every transfer in real-time.
Try this next
MATCH (a:Account)-[t:TRANSFER]->(b:Account)
RETURN a.country AS from_country, b.country AS to_country,
count(t) AS transfers, sum(t.amount) AS volume
ORDER BY volume DESC;
// Every account that sits on any loop of 2-4 hops, whatever the amounts.
MATCH p = (a:Account)-[:TRANSFER*1..3]->(last:Account)
MATCH (last)-[:TRANSFER]->(back:Account)
WHERE back.id = a.id
RETURN DISTINCT a.id, a.holder;
All eight accounts come back: money moves in loops in ordinary business too. The amount
threshold in Step 2 is what separates a laundering ring from a normal payment loop.
MATCH (a:Account)-[t:TRANSFER]->(b:Account)
WHERE t.amount > 70000
RETURN a.holder AS sender, b.holder AS receiver, t.amount AS amount, t.date AS date
ORDER BY date;