Skip to content

SynapCores / Recipe

Anti-money-laundering layering detection

Surface multi-hop transaction chains that obscure source-of-funds, longer than 3 hops with high values

All recipes· graph· 9 minutesadvancedcypher
Instance: localhost:8080

Opens your running SynapCores (Anti-money-laundering layering detection will be staged for a preview — nothing runs until you click Run). No instance yet? Install free in ~30s.

Share

Objective

Layering is the AML stage where dirty money is moved through a long chain of accounts to obscure its origin. Compliance teams need to find chains longer than three hops where the value preserved end-to-end is high. The graph view turns this into a single MATCH that traces money flow across banks, jurisdictions, and shell companies in one shot.

Step 1: Create the graph

// Accounts (with bank + jurisdiction)
MERGE (a:Account {id: "ACC-A", holder: "Sarah Chen",     bank: "Bay Trust",    country: "US"})
MERGE (b:Account {id: "ACC-B", holder: "Helios Holdings", bank: "Bay Trust",   country: "US"})
MERGE (c:Account {id: "ACC-C", holder: "Vortex Trading",  bank: "Cayman Bank", country: "KY"})
MERGE (d:Account {id: "ACC-D", holder: "BlueRock Ltd",    bank: "Riga Bank",   country: "LV"})
MERGE (e:Account {id: "ACC-E", holder: "Atlas Shell",     bank: "Sofia Bank",  country: "BG"})
MERGE (f:Account {id: "ACC-F", holder: "Mia Rossi",       bank: "Banca Roma",  country: "IT"})
MERGE (g:Account {id: "ACC-G", holder: "Polar Imports",   bank: "Tallin Bank", country: "EE"})
MERGE (h:Account {id: "ACC-H", holder: "Leo Park",        bank: "Seoul Bank",  country: "KR"})
MERGE (i:Account {id: "ACC-I", holder: "Eli Tanaka",      bank: "Tokyo Bank",  country: "JP"})

// Transfer edges (amount in USD, date)
// Layered chain: A -> B -> C -> D -> E -> F (5 hops, high preserved value)
MERGE (a)-[:TRANSFER {amount: 250000, date: "2026-04-01"}]->(b)
MERGE (b)-[:TRANSFER {amount: 245000, date: "2026-04-03"}]->(c)
MERGE (c)-[:TRANSFER {amount: 240000, date: "2026-04-05"}]->(d)
MERGE (d)-[:TRANSFER {amount: 235000, date: "2026-04-07"}]->(e)
MERGE (e)-[:TRANSFER {amount: 230000, date: "2026-04-09"}]->(f)

// Second suspicious chain: G -> H -> I (only 2 hops; should NOT trigger)
MERGE (g)-[:TRANSFER {amount: 180000, date: "2026-04-02"}]->(h)
MERGE (h)-[:TRANSFER {amount: 178000, date: "2026-04-04"}]->(i)

// Decoy noise transfers
MERGE (a)-[:TRANSFER {amount: 1200,  date: "2026-04-10"}]->(g)
MERGE (h)-[:TRANSFER {amount: 800,   date: "2026-04-12"}]->(b)
MERGE (f)-[:TRANSFER {amount: 50,    date: "2026-04-15"}]->(i);

Step 2: Detect layering chains

// Look for chains of length >3 where every hop is over $50k AND >85% of the value
// is preserved end-to-end (a hallmark of pass-through layering).
MATCH path = (src:Account)-[t:TRANSFER*4..6]->(dst:Account)
WHERE ALL(r IN t WHERE r.amount > 50000)
WITH src, dst, path, t,
     head(t).amount AS source_amount,
     last(t).amount AS final_amount
WHERE toFloat(final_amount) / source_amount > 0.85
WITH src, dst, path,
     [n IN nodes(path) | n.id]              AS account_chain,
     [n IN nodes(path) | n.country]         AS country_chain,
     length(path)                            AS hop_count,
     reduce(s = 0, r IN relationships(path) | s + r.amount) AS total_volume
RETURN account_chain, country_chain, hop_count, total_volume
ORDER BY hop_count DESC, total_volume DESC;

What's happening

  • TRANSFER*4..6 only matches chains of at least four hops — exactly where layering lives. Direct transfers and 2-hop "round-trip" patterns are ignored.
  • head(t) and last(t) access the first and last edges of the path to compute value retention, the strongest layering signal: if 90% of $250k arrives at the destination, it likely passed through unchanged.
  • country_chain reveals jurisdiction-hopping (US → KY → LV → BG → IT here) — auditors immediately see the geographic footprint.
  • In SQL this is essentially impossible without an iterative loop in application code — recursive CTEs that emit path arrays are a Postgres-specific extension and still require complex window arithmetic for retention scoring.
  • Run this incrementally on every new transfer to alert the FIU within seconds, not after a batch.

Try this next

MATCH (a:Account)-[t:TRANSFER]->(b:Account)
WHERE a.country <> b.country AND t.amount > 100000
RETURN a.country AS from_country, b.country AS to_country,
       count(t) AS transfers, sum(t.amount) AS volume
ORDER BY volume DESC;
MATCH path = (a:Account)-[:TRANSFER*3..]->(z:Account)
WHERE NOT (z)-[:TRANSFER]->()
RETURN a.holder AS originator, z.holder AS terminus, length(path) AS hops
ORDER BY hops DESC LIMIT 5;
MATCH (a:Account)-[t:TRANSFER]->(b:Account)
WITH a, count(t) AS out_degree
WHERE out_degree >= 3
RETURN a.id, a.holder, a.country, out_degree
ORDER BY out_degree DESC;

Tags

graphcypheramlfraudadvanced