Domain Knowledge: Money Laundering Patterns on Public Ledgers
What money laundering looks like on a public ledger: the three stages, FATF's virtual asset red flags, the on-chain typologies analysts look for (peel chains, fan-out and fan-in, pass-through accounts, cycles, asset hopping), how each appears on the XRP Ledger, and how to turn them into features and an explainable first model.
Prerequisites
Complete these before starting this module:
What you'll learn
- Explain placement, layering and integration, and where each stage is visible on a public ledger.
- Name the FATF virtual asset red flags that translate into on-chain features.
- Recognise peel chains, fan-out and fan-in, pass-through accounts, cycles, asset hopping and fresh-account batches in XRPL data.
- Engineer behavioural features and transparent rules from typologies, and rank accounts with an unsupervised model.
- Avoid the common pitfalls: flagging exchanges, dust, window effects, label leakage and naming individuals.
Overview
You can now download XRPL data and turn it into a graph. The question the hackathon asks is harder: which parts of that graph look like money laundering? This module gives you the domain knowledge to answer it, so your model looks for patterns investigators actually care about instead of whatever happens to be statistically odd.
You'll learn the three stages of laundering, the red flags regulators publish, the on-chain typologies that blockchain analysts look for, how each one shows up on the XRP Ledger specifically, and how to turn them into features. You'll finish with a script that scores accounts with transparent rules and an unsupervised model. In the live session we walk through the script together; run it yourself before the hackathon.
One principle runs through all of it: a pattern is a reason to look closer, never proof of a crime. Your model produces a review queue, not a list of criminals.
What money laundering is
Money laundering is the process of making the proceeds of crime look legitimate. The United Nations Office on Drugs and Crime describes it in three stages:
- Placement: getting illicit funds into the financial system. On a public ledger, this is often the moment stolen or scam funds first land in an account.
- Layering: moving value through many transactions, accounts and assets to obscure where it came from. This is where on-chain analysis has the most to see, because every hop is public.
- Integration: bringing the value back as apparently clean funds, typically by cashing out through an exchange or service.
Real cases don't always follow three neat steps, but the model tells you where to look: the entry point, the obfuscation in the middle, and the exit.
What regulators look for
The Financial Action Task Force (FATF), the global standard-setter for anti-money laundering, published Virtual Assets Red Flag Indicators in September 2020, drawn from more than 100 real cases. It groups indicators into seven categories: transaction size and frequency, transaction patterns, anonymity-enhancing features, sender and recipient profiles, source of funds, geographic risk, and reporting obligations.
The on-chain ones translate almost directly into features:
- Many small transfers apparently structured to stay under reporting thresholds.
- Funds moved rapidly through several accounts with no apparent economic reason.
- Multiple accounts that appear to be controlled by the same person.
- Value flowing to or from high-risk services, or through tools designed to obscure the trail.
- Activity that is out of character for the account's history.
FATF is explicit that these are indicators, not proof: context and combinations matter.
On-chain typologies
Blockchain analytics firms and researchers describe recurring shapes in transaction graphs. These are the ones you should know.
Peel chain. Funds move through a long chain of fresh accounts. At each hop a small amount is "peeled" off to a cash-out point and the rest moves on to the next new account. On a graph it looks like a long thin path with small branches.
Fan-out, then fan-in (smurfing). A large sum is split across many accounts, often in similar-sized pieces, and later consolidated again. Deliberately splitting to stay under thresholds is called structuring.
Pass-through (mule) accounts. An account receives value and forwards almost all of it within minutes, keeping nothing. Legitimate accounts rarely behave like this; layering hops do.
Round-tripping (cycles). Value leaves an account and returns to it, directly or through intermediaries, to fake activity or confuse tracing.
Asset hopping. Value changes form along the way, from XRP to a token and back, or across chains through bridges, so a tracer following one asset loses the trail.
Wash trading. Trading with yourself to create fake volume or set a price. It's primarily market manipulation, but it can be combined with laundering, for example by "buying" an NFT from yourself at an inflated price.
Fresh-account batches. A group of new accounts created by the same funding parent within minutes, each used briefly and then abandoned or deleted.
How the typologies look on the XRP Ledger
The XRPL has no mixers or privacy features at the protocol level, and its native features shape how laundering shows up:
| Typology | What to look for on the XRPL |
|---|---|
| Pass-through | Payment in, Payment out of nearly equal value, often within the same few ledgers (seconds apart) |
| Peel chain | A path of accounts, each with one main input and two outputs, one small and one large to a brand-new account |
| Fan-out / fan-in | High out-degree from one account, or high in-degree to one account, with uniform amounts |
| Fresh-account batches | Many accounts whose first transaction is a Payment from the same parent, close in time; later AccountDelete |
| Asset hopping | Cross-currency Payments, OfferCreate crossings and AMM swaps between a receipt and a cash-out |
| Cash-out | Payments to known exchange addresses, each with a destination tag that identifies one customer at that exchange |
| Delayed movement | Escrows and checks that release value later, outside a short analysis window |
Three XRPL-specific details matter for interpretation:
- Destination tags: when funds reach an exchange, the tag is what tells the exchange which customer to credit. Investigators pass the (address, tag) pair to the exchange; only the exchange can tie it to an identity.
- Compliance controls exist for issued tokens: issuers can freeze trust lines and, if enabled before issuing, claw back tokens. Neither applies to native XRP, and no transaction can be reversed after validation.
- Account settings are signals:
RequireDestandDepositAuthare typical of services that care about attribution.
Learning from real incidents
Public reporting on XRP Ledger incidents shows the cash-out end of the lifecycle clearly.
- January 2024: around 213 million XRP (about US$112 million at the time) was stolen from personal accounts of Ripple co-founder Chris Larsen. Investigator ZachXBT flagged the movement on-chain, and reports said the funds were sent on to several exchanges, including MEXC, Gate, Binance and Kraken. The public ledger made it possible to alert exchanges within hours.
- June 2019: the GateHub wallet service reported a breach of about 23 million XRP.
- April 2025: malicious versions of the official
xrplnpm package were published and sent private keys to an attacker. It's a reminder that key theft, the placement stage, often starts off-chain.
Use incidents like these to sanity-check your model: would it have flagged the movement between the theft and the exchange deposits?
Turning typologies into features
A good detection approach for a one-day hackathon has three layers, from most to least explainable:
- Behavioural features per account: counts, totals, distinct counterparties, pass-through ratio, time between receiving and forwarding, uniformity of outgoing amounts, account age, funding parent, share of dust.
- Transparent rules built from those features: "forwarded at least 90% within 10 minutes", "sent to 10 or more counterparties in uniform amounts". These are easy to explain to judges and investigators.
- A model on top: an unsupervised anomaly detector such as an Isolation Forest when you have no labels, or graph methods (graph features, graph neural networks) when you do.
Why unsupervised first? There's no labelled XRPL laundering dataset. Even the best-known research dataset, the Elliptic Bitcoin graph (about 200,000 transactions), labels only around 2% as illicit and most of the rest as unknown. Treating "unknown" as "clean" is one of the most common evaluation mistakes in this field.
Exercise: score accounts by typology
This script reads xrp_payments.csv from the previous module, sets dust aside, computes typology features, applies transparent flags, finds short cycles, and ranks accounts with an Isolation Forest. In the live session, the 50-ledger dataset from Exercise 3 is enough to see it work; before the hackathon, rerun Exercise 3 over 100 to 200 ledgers for a richer result.
"""Exercise 4: turn laundering typologies into features and rank unusual accounts.
Run: python ex3_flow_graph.py 200 (creates xrp_payments.csv)
python ex4_typology_features.py
Needs: pip install pandas networkx scikit-learn numpy
"""
import networkx as nx
import numpy as np
import pandas as pd
from sklearn.ensemble import IsolationForest
DUST_XRP = 0.001 # payments below this are usually spam or "dusting", not value transfer
QUICK_FORWARD_MIN = 10 # forwarding within 10 minutes of receiving counts as "rapid"
df = pd.read_csv("xrp_payments.csv", parse_dates=["time"])
print(f"Loaded {len(df)} payments")
# Step 1: separate dust from value. Dust spam dominates raw XRPL graphs and drowns the signal.
dust = df[df.xrp < DUST_XRP]
value = df[df.xrp >= DUST_XRP].copy()
print(f"{len(dust)} dust payments set aside, {len(value)} value payments kept")
# Step 2: per-account behavioural features. Each one maps to a typology in this module.
out = value.groupby("src").agg(out_n=("xrp", "size"), out_xrp=("xrp", "sum"),
out_cpty=("dst", "nunique"), first_out=("time", "min"),
out_size_cv=("xrp", lambda s: s.std() / s.mean() if len(s) > 1 else np.nan))
inc = value.groupby("dst").agg(in_n=("xrp", "size"), in_xrp=("xrp", "sum"),
in_cpty=("src", "nunique"), first_in=("time", "min"),
in_tags=("tag", "nunique"))
feat = out.join(inc, how="outer")
feat[["out_n", "out_xrp", "out_cpty", "in_n", "in_xrp", "in_cpty", "in_tags"]] = \
feat[["out_n", "out_xrp", "out_cpty", "in_n", "in_xrp", "in_cpty", "in_tags"]].fillna(0)
# Pass-through: how much of what came in went straight back out (mule / layering hop).
both = (feat.in_xrp > 0) & (feat.out_xrp > 0)
feat["pass_through"] = 0.0
feat.loc[both, "pass_through"] = (feat.loc[both, ["in_xrp", "out_xrp"]].min(axis=1)
/ feat.loc[both, ["in_xrp", "out_xrp"]].max(axis=1))
# Minutes between first receipt and first send (rapid movement, FATF red flag).
feat["minutes_to_forward"] = (feat.first_out - feat.first_in).dt.total_seconds() / 60
# Fan-out / fan-in: many distinct counterparties (smurfing out, consolidation in).
feat["fan_out"] = feat.out_cpty
feat["fan_in"] = feat.in_cpty
# Structuring: many outgoing payments of near-identical size (low coefficient of variation).
feat["uniform_splits"] = (feat.out_n >= 5) & (feat.out_size_cv.fillna(1) < 0.05)
# Step 3: transparent rule-based flags. Judges and investigators can read these.
feat["flag_rapid_pass_through"] = (feat.pass_through >= 0.9) & feat.minutes_to_forward.between(0, QUICK_FORWARD_MIN)
feat["flag_fan_out"] = feat.fan_out >= 10
feat["flag_fan_in"] = feat.fan_in >= 10
feat["flag_structuring"] = feat.uniform_splits
flags = [c for c in feat.columns if c.startswith("flag_")]
feat["rule_score"] = feat[flags].sum(axis=1)
# Step 4: cycles. Value that returns to where it started within a few hops (round-tripping).
G = nx.DiGraph()
G.add_weighted_edges_from(value.groupby(["src", "dst"]).xrp.sum().reset_index().values, weight="xrp")
cycles = [c for c in nx.simple_cycles(G, length_bound=4) if len(c) > 1]
in_cycle = {a for c in cycles for a in c}
feat["in_short_cycle"] = feat.index.isin(in_cycle)
print(f"{len(cycles)} short cycles (2 to 4 hops) found")
# Step 5: an unsupervised baseline. No labels, so we rank by "unusualness", not guilt.
cols = ["out_n", "out_xrp", "out_cpty", "in_n", "in_xrp", "in_cpty", "pass_through"]
X = np.log1p(feat[cols].clip(lower=0))
model = IsolationForest(n_estimators=300, contamination="auto", random_state=42).fit(X)
feat["anomaly_score"] = -model.score_samples(X) # higher means more unusual
ranked = feat.sort_values(["rule_score", "anomaly_score"], ascending=False)
ranked.to_csv("account_features.csv")
print("\nFlag counts:")
print(feat[flags + ["in_short_cycle"]].sum().to_string())
print("\nTop 10 accounts to review (a review queue, not an accusation):")
print(ranked[["rule_score", "anomaly_score", "in_xrp", "out_xrp", "fan_in", "fan_out",
"pass_through", "minutes_to_forward"]].head(10).round(3).to_string())
Read the top of the ranking critically. Is the "most anomalous" account a launderer, or a market maker, an exchange hot wallet or a spam bot? That question, answered with evidence, is what separates a strong hackathon project from a weak one.
Pitfalls to avoid
- Exchanges look anomalous. Hot wallets have huge fan-in and fan-out. Label known services and treat them as a separate class instead of flagging them.
- Dust dominates. Remove or separately model payments that carry no real value.
- Windows cut patterns in half. A peel chain spread over two days is invisible in a one-hour window.
- Leakage in evaluation. If you build labels from a rule, don't report the same rule's accuracy as your model's performance.
- Naming and shaming. Never publish an address as "criminal" in your slides. Say "flagged for review" and show the evidence.
Check
You're done when you can explain placement, layering and integration, name five on-chain typologies and how each appears on the XRPL, and justify why your first model should be explainable. Then take the quiz and run the scoring exercise.
Resources
Money laundering overview — UNODC
article· unodc.org
Virtual Assets Red Flag Indicators — FATF (2020)
doc· fatf-gafi.org
Elliptic Data Set paper — Weber et al. (2019)
article· arxiv.org
Elliptic2: the shape of money laundering (2024)
article· arxiv.org
Chainalysis Crypto Crime Report
article· chainalysis.com
Freezing tokens — XRPL Docs
doc· xrpl.org
Clawing back tokens — XRPL Docs
doc· xrpl.org
Deposit authorization — XRPL Docs
doc· xrpl.org
scikit-learn IsolationForest
doc· scikit-learn.org
Assignments
0 of 2 complete