ChainTrail — Open Source Bitcoin AML Forensics
I spent the last few weeks building ChainTrail, a real-time Bitcoin
transaction monitoring and AML forensics platform. The interesting part?
I built and run the entire thing on my Android phone using Termux.
What it does
ChainTrail connects to the live Bitcoin mempool via WebSocket and lets
investigators trace fund flows across the blockchain graph.
Live demo: https://cary-uncompensated-joanie.ngrok-free.dev
Key features
- Live mempool stream via mempool.space WebSocket
- Transaction graph tracer using PostgreSQL recursive CTEs
- Interactive D3.js graph visualization
- 1,258+ threat intel entries (WannaCry, Lazarus Group, Silk Road, OFAC)
- ML risk scoring 0-100 with feature extraction
- Address clustering using Union-Find heuristic
- Pattern detection: layering, smurfing, fan-out, peeling chain
- Case management with PDF report export
- Ethereum support with Tornado Cash detection
- Webhook alerts via Telegram and Discord
Tech stack
- Backend: Node.js + TypeScript + Express
- Database: PostgreSQL (recursive CTEs for graph traversal)
- Cache: Redis
- Frontend: Next.js + Tailwind + D3.js
- Data: mempool.space WebSocket
**The interesting part — graph traversal
**
The core of ChainTrail is a PostgreSQL recursive CTE that walks the
Bitcoin transaction graph:
sql
WITH RECURSIVE tx_graph AS
(
SELECT i.from_address, t.txid, o.value_satoshi, 1 AS hop,
ARRAY[i.from_address] AS path
FROM tx_inputs i
JOIN transactions t ON t.txid = i.txid
JOIN tx_outputs o ON o.txid = t.txid
WHERE o.to_address = $1
UNION ALL
SELECT i.from_address, t.txid, o.value_satoshi, g.hop + 1,
g.path || i.from_address
FROM tx_graph g
JOIN tx_inputs i ON ...
WHERE g.hop < $2
AND NOT (i.from_address = ANY(g.path))
)
SELECT * FROM tx_graph ORDER BY hop;
This lets you trace any Bitcoin address N hops backward to find the
origin of funds — identifying mixers, exchanges, and sanctioned wallets
along the way.
**ML Risk Scoring**
Each address gets a risk score 0-100 based on extracted features:
Direct threat intel match → +40 to +100
Proximity to bad actor (hop distance) → decays with distance
Fan-out pattern (sending to many addresses) → +20
Fan-in pattern (receiving from many) → +15
Known exchange → -30 (reduces false positives)
Cluster risk level → +5 to +50
**What I learned**
PostgreSQL recursive CTEs are surprisingly powerful for graph
traversal — no graph database needed
mempool.space WebSocket pushes ~6 new transactions every few seconds
Union-Find is the right data structure for address clustering
Running a full stack on Android is actually practical for development
**What's next**
OFAC sanctions auto-sync from US Treasury
Better ML model trained on labeled data
Lightning Network support
Deploy to cloud for production use
Links
GitHub: https://github.com/rajeshselvam02/chaintail
Live demo: https://cary-uncompensated-joanie.ngrok-free.dev
Pricing: https://chaintail-landing.vercel.app
Built with ❤️ by Rajesh Selvam
---
Top comments (0)