crypttrace 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crypttrace-0.2.0/LICENSE +21 -0
- crypttrace-0.2.0/PKG-INFO +503 -0
- crypttrace-0.2.0/README.md +469 -0
- crypttrace-0.2.0/pyproject.toml +55 -0
- crypttrace-0.2.0/setup.cfg +4 -0
- crypttrace-0.2.0/src/crypttrace/__init__.py +2 -0
- crypttrace-0.2.0/src/crypttrace/addresses.py +132 -0
- crypttrace-0.2.0/src/crypttrace/analysis.py +222 -0
- crypttrace-0.2.0/src/crypttrace/assess.py +273 -0
- crypttrace-0.2.0/src/crypttrace/assets.py +68 -0
- crypttrace-0.2.0/src/crypttrace/bridges.py +90 -0
- crypttrace-0.2.0/src/crypttrace/chains.py +254 -0
- crypttrace-0.2.0/src/crypttrace/cli.py +795 -0
- crypttrace-0.2.0/src/crypttrace/config.py +27 -0
- crypttrace-0.2.0/src/crypttrace/fetchers/__init__.py +0 -0
- crypttrace-0.2.0/src/crypttrace/fetchers/bitcoin.py +186 -0
- crypttrace-0.2.0/src/crypttrace/fetchers/etherscan.py +114 -0
- crypttrace-0.2.0/src/crypttrace/fetchers/http.py +139 -0
- crypttrace-0.2.0/src/crypttrace/fetchers/solana.py +97 -0
- crypttrace-0.2.0/src/crypttrace/fetchers/tron.py +128 -0
- crypttrace-0.2.0/src/crypttrace/funder.py +76 -0
- crypttrace-0.2.0/src/crypttrace/investigate.py +204 -0
- crypttrace-0.2.0/src/crypttrace/labels/__init__.py +0 -0
- crypttrace-0.2.0/src/crypttrace/labels/audit.py +102 -0
- crypttrace-0.2.0/src/crypttrace/labels/known.json +148 -0
- crypttrace-0.2.0/src/crypttrace/labels/labels.py +199 -0
- crypttrace-0.2.0/src/crypttrace/labels/partial.json +15 -0
- crypttrace-0.2.0/src/crypttrace/offramp.py +58 -0
- crypttrace-0.2.0/src/crypttrace/prices.py +90 -0
- crypttrace-0.2.0/src/crypttrace/render.py +241 -0
- crypttrace-0.2.0/src/crypttrace/report.py +175 -0
- crypttrace-0.2.0/src/crypttrace/store.py +215 -0
- crypttrace-0.2.0/src/crypttrace/trace.py +165 -0
- crypttrace-0.2.0/src/crypttrace/verify.py +153 -0
- crypttrace-0.2.0/src/crypttrace/watch.py +138 -0
- crypttrace-0.2.0/src/crypttrace/web/index.html +660 -0
- crypttrace-0.2.0/src/crypttrace/webapp.py +292 -0
- crypttrace-0.2.0/src/crypttrace.egg-info/PKG-INFO +503 -0
- crypttrace-0.2.0/src/crypttrace.egg-info/SOURCES.txt +41 -0
- crypttrace-0.2.0/src/crypttrace.egg-info/dependency_links.txt +1 -0
- crypttrace-0.2.0/src/crypttrace.egg-info/entry_points.txt +2 -0
- crypttrace-0.2.0/src/crypttrace.egg-info/requires.txt +6 -0
- crypttrace-0.2.0/src/crypttrace.egg-info/top_level.txt +1 -0
crypttrace-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 samsepiol09
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crypttrace
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: OSINT toolkit for crypto investigations: trace stolen funds across Ethereum, Bitcoin, Tron and Solana
|
|
5
|
+
Author: bobslayerX
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bobslayerX/crypttrace
|
|
8
|
+
Project-URL: Issues, https://github.com/bobslayerX/crypttrace/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/bobslayerX/crypttrace/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: osint,blockchain,forensics,cryptocurrency,bitcoin,ethereum,tron,solana,investigation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: Intended Audience :: Legal Industry
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Security
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: typer>=0.12
|
|
29
|
+
Requires-Dist: rich>=13.0
|
|
30
|
+
Requires-Dist: requests>=2.31
|
|
31
|
+
Provides-Extra: web
|
|
32
|
+
Requires-Dist: flask>=3.0; extra == "web"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# crypttrace
|
|
36
|
+
|
|
37
|
+
[](https://github.com/bobslayerX/crypttrace/actions/workflows/selftest.yml)
|
|
38
|
+
[](https://pypi.org/project/crypttrace/)
|
|
39
|
+
[](https://pypi.org/project/crypttrace/)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
|
|
42
|
+
Open-source OSINT toolkit for crypto investigations. Give it a suspicious
|
|
43
|
+
address; it pulls the public on-chain history, labels known entities (exchanges,
|
|
44
|
+
mixers, sanctioned wallets), scores risk, and maps where the funds moved —
|
|
45
|
+
across Ethereum, Bitcoin, Tron, Solana and more.
|
|
46
|
+
|
|
47
|
+
Built for the investigation that actually happens: *someone's crypto was stolen,
|
|
48
|
+
here's the wallet — where did the money go, and where can it still be stopped?*
|
|
49
|
+
|
|
50
|
+
Works as a terminal tool **and** as a local web app with an interactive
|
|
51
|
+
fund-flow graph.
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
*Real case: the AFX Trade exploiter (July 2026, $24.15M). crypttrace follows the
|
|
56
|
+
stolen ETH out of the attacker's wallet — 12,467 ETH (~$23.3M) to a holding
|
|
57
|
+
address, then split across three wallets and split again. Classic layering,
|
|
58
|
+
mapped in seconds.*
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pipx install "crypttrace[web]" # or: pip install "crypttrace[web]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`[web]` also installs Flask for the web UI; leave it off for the terminal tool
|
|
69
|
+
alone. Python 3.9 or newer. To work on the code instead:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git clone https://github.com/bobslayerX/crypttrace
|
|
73
|
+
cd crypttrace
|
|
74
|
+
pip install -e ".[web]"
|
|
75
|
+
python selftest.py # offline checks, no API keys needed
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### API keys — what's actually required
|
|
79
|
+
|
|
80
|
+
| | Needed? | How to get it |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| **EVM chains** (Ethereum, BSC, Polygon, Arbitrum, Optimism, Base) | **Required** | Free key at [etherscan.io/myapikey](https://etherscan.io/myapikey). One v2 key covers all EVM chains. |
|
|
83
|
+
| **Bitcoin, Tron, Solana** | **Not required** | Work out of the box via public endpoints. |
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export ETHERSCAN_API_KEY=xxxx # required for EVM chains only
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
On Windows (PowerShell):
|
|
90
|
+
|
|
91
|
+
```powershell
|
|
92
|
+
$env:ETHERSCAN_API_KEY = "xxxx" # this window only
|
|
93
|
+
[Environment]::SetEnvironmentVariable("ETHERSCAN_API_KEY", "xxxx", "User") # permanently
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**About the keyless chains:** Bitcoin, Tron and Solana work with no signup at
|
|
97
|
+
all, but their free public endpoints are **rate-limited**. crypttrace handles
|
|
98
|
+
this for you — it caches every response, spaces requests out, and retries with
|
|
99
|
+
backoff when a limit is hit. For deep traces you can raise the ceiling with your
|
|
100
|
+
own credentials (all optional):
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
export TRONGRID_API_KEY=xxxx # optional: higher TronGrid quota
|
|
104
|
+
export CRYPTTRACE_SOLANA_RPC=https://… # optional: your own (faster) Solana RPC
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
(In PowerShell: `$env:TRONGRID_API_KEY = "xxxx"` and so on.)
|
|
108
|
+
|
|
109
|
+
## Supported chains
|
|
110
|
+
|
|
111
|
+
| Chain | `--chain` | Data source | Model |
|
|
112
|
+
|---|---|---|---|
|
|
113
|
+
| Ethereum, BSC, Polygon, Arbitrum, Optimism, Base | `eth` `bsc` `polygon` `arbitrum` `optimism` `base` | Etherscan v2 | account |
|
|
114
|
+
| Bitcoin | `btc` | mempool.space | UTXO |
|
|
115
|
+
| Tron (TRX + USDT-TRC20) | `tron` | TronGrid | account |
|
|
116
|
+
| Solana (SOL + SPL) | `sol` | public JSON-RPC | account |
|
|
117
|
+
|
|
118
|
+
Every network is normalized to the same transfer shape, so `profile`, `trace`,
|
|
119
|
+
`funder`, `report` and the web graph behave identically everywhere. Bitcoin
|
|
120
|
+
additionally unlocks `cluster`. Tron matters for everyday victim cases — most
|
|
121
|
+
romance / "pig butchering" scams move USDT-TRC20 because fees are near zero.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Web UI
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
crypttrace serve # then open http://127.0.0.1:8000
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Paste an address, pick a chain and direction, and get an interactive fund-flow
|
|
132
|
+
graph: nodes coloured by what they are, arrows showing where value went, click a
|
|
133
|
+
node for its full profile, click a transfer line to open it on the block
|
|
134
|
+
explorer. A side panel shows the profile, first-funder chain and off-ramp check.
|
|
135
|
+
Everything runs on your machine — nothing is uploaded anywhere.
|
|
136
|
+
|
|
137
|
+
This exists so non-technical victims can use the tool at all: a form and a
|
|
138
|
+
picture, instead of command-line flags.
|
|
139
|
+
|
|
140
|
+
**Both directions of the same investigation.** Forward — where the stolen money
|
|
141
|
+
went:
|
|
142
|
+
|
|
143
|
+

|
|
144
|
+
|
|
145
|
+
Backward — how that wallet was funded in the first place, which is how you tie
|
|
146
|
+
an anonymous wallet to something identifiable:
|
|
147
|
+
|
|
148
|
+

|
|
149
|
+
|
|
150
|
+
## If your crypto was stolen — start here
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
crypttrace investigate 0xADDRESS_THE_FUNDS_WENT_TO
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
One command. It follows the money, works out where it ended up, saves a case
|
|
157
|
+
file you can send to an exchange or attach to a police report, and then explains
|
|
158
|
+
**in plain language what to do next** — which exchange to contact, where to file,
|
|
159
|
+
how to keep watching the money, and how to avoid the "recovery services" that
|
|
160
|
+
target victims a second time.
|
|
161
|
+
|
|
162
|
+
It is also honest with you: most stolen crypto is not recovered, and what
|
|
163
|
+
matters is speed and whether the funds touch a regulated exchange. The tool
|
|
164
|
+
gives you evidence and timing — it cannot move funds or name a person by itself.
|
|
165
|
+
|
|
166
|
+
## Commands
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# The one-command investigation (recommended starting point)
|
|
170
|
+
crypttrace investigate 0xADDRESS --chain eth
|
|
171
|
+
|
|
172
|
+
# What is this address? (local label DB, works offline)
|
|
173
|
+
crypttrace label 0x28c6c06298d514db089934071355e5743bf21d60
|
|
174
|
+
|
|
175
|
+
# Balance, activity window, risk, top counterparties — on any chain
|
|
176
|
+
crypttrace profile 0xADDRESS --chain eth
|
|
177
|
+
crypttrace profile bc1qADDRESS --chain btc
|
|
178
|
+
|
|
179
|
+
# Follow the money, hop by hop, as a coloured tree
|
|
180
|
+
crypttrace trace 0xADDRESS --depth 4 --branching 3
|
|
181
|
+
|
|
182
|
+
# Trace backwards instead: where did this wallet's funds come FROM?
|
|
183
|
+
crypttrace trace 0xADDRESS --direction in
|
|
184
|
+
|
|
185
|
+
# Trace a token rather than the native coin (most thefts are stablecoins)
|
|
186
|
+
crypttrace trace 0xADDRESS --asset usdt --depth 4
|
|
187
|
+
|
|
188
|
+
# Token holdings, valued in USD
|
|
189
|
+
crypttrace tokens 0xADDRESS
|
|
190
|
+
|
|
191
|
+
# Who bootstrapped this wallet's first gas? Follow it toward a KYC point
|
|
192
|
+
crypttrace funder 0xADDRESS --hops 6
|
|
193
|
+
|
|
194
|
+
# Is this an exchange deposit address (i.e. the cash-out point)?
|
|
195
|
+
crypttrace offramp 0xADDRESS
|
|
196
|
+
|
|
197
|
+
# Bitcoin only: find other wallets owned by the same person
|
|
198
|
+
crypttrace cluster bc1qADDRESS
|
|
199
|
+
|
|
200
|
+
# Who fed this wallet? In a mass theft, that's the victim list → CSV
|
|
201
|
+
crypttrace victims bc1qADDRESS --chain btc --depth 1 -o victims.csv
|
|
202
|
+
|
|
203
|
+
# When did the money move? Spots automated sweeps vs ordinary use
|
|
204
|
+
crypttrace timeline bc1qADDRESS --chain btc
|
|
205
|
+
|
|
206
|
+
# Follow funds across bridges into other chains
|
|
207
|
+
crypttrace crosschain 0xADDRESS --window 48
|
|
208
|
+
|
|
209
|
+
# Watch addresses; alert loudly the moment funds head for an exchange
|
|
210
|
+
crypttrace watch add 0xADDRESS --note "my stolen ETH"
|
|
211
|
+
crypttrace watch run --interval 300 # or --once for cron / Task Scheduler
|
|
212
|
+
|
|
213
|
+
# Full investigation report to disk (Markdown + JSON)
|
|
214
|
+
crypttrace report 0xADDRESS --depth 3
|
|
215
|
+
|
|
216
|
+
# Refresh label lists (OFAC sanctions, …)
|
|
217
|
+
crypttrace update-labels
|
|
218
|
+
|
|
219
|
+
# Launch the web UI / list chains
|
|
220
|
+
crypttrace serve
|
|
221
|
+
crypttrace chains
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Example trace output:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
🔴 0x098B716B…3E2f96 [Ronin Bridge Exploiter (Lazarus)]
|
|
228
|
+
├── ──33568.15 ETH (1 tx) ≈$63.8M──▶ 🔴 0x35fb6f6d…26d4b1 [OFAC SDN (sanctioned)]
|
|
229
|
+
│ └── ↳ trail ends here (identifiable entity — subpoena / off-chain)
|
|
230
|
+
└── ──25127.51 ETH (1 tx) ≈$47.7M──▶ ⚪ 0xf7b31119…5cf1be
|
|
231
|
+
└── ──4100.00 ETH (41 tx) ≈$7.8M──▶ 🟣 Tornado Cash: 0.1 ETH
|
|
232
|
+
└── ↳ trail ends here (identifiable entity — subpoena / off-chain)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Legend: 🟢 exchange · 🟣 mixer · 🔴 sanctioned/scam · 🌉 bridge · ⚪ unknown
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Capabilities in depth
|
|
240
|
+
|
|
241
|
+
### Tracing (forward and backward)
|
|
242
|
+
|
|
243
|
+
`trace` follows the largest transfers recursively and stops at identifiable
|
|
244
|
+
entities — that's the OSINT handoff point. `--direction out` (default) answers
|
|
245
|
+
"where did the money go"; `--direction in` answers "where did this wallet's
|
|
246
|
+
money come from", which is how you vet a suspicious address or find a victim's
|
|
247
|
+
source of funds.
|
|
248
|
+
|
|
249
|
+
### Watch & alerts — catching the cash-out
|
|
250
|
+
|
|
251
|
+
The only window to freeze stolen funds is the moment they reach an exchange
|
|
252
|
+
deposit. Victims can't monitor a chain 24/7. `watch` keeps a list of addresses,
|
|
253
|
+
detects new activity and raises a **loud HIGH alert** the instant funds move
|
|
254
|
+
toward an exchange (directly, or to a detected deposit address); quieter notices
|
|
255
|
+
for other movement. It only alerts on activity *after* an address is added, and
|
|
256
|
+
never double-alerts.
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
crypttrace watch add 0xADDRESS --note "victim funds"
|
|
260
|
+
crypttrace watch list
|
|
261
|
+
crypttrace watch run --interval 300 # continuous
|
|
262
|
+
crypttrace watch run --once # single check, for scheduled tasks
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Optional Telegram alerts: set `CRYPTTRACE_TG_TOKEN` and `CRYPTTRACE_TG_CHAT`,
|
|
266
|
+
then pass `--telegram`.
|
|
267
|
+
|
|
268
|
+
### Off-ramp detection
|
|
269
|
+
|
|
270
|
+
Laundered funds reaching an exchange land on a per-user *deposit address* —
|
|
271
|
+
there are millions, so none appear in any label list. `offramp` spots them by
|
|
272
|
+
behaviour: an address forwarding most of its outgoing value to a labelled
|
|
273
|
+
exchange is almost certainly a deposit address, i.e. the cash-out point where
|
|
274
|
+
that exchange holds the depositor's KYC. `trace` applies the same heuristic
|
|
275
|
+
automatically, turning an anonymous intermediary into "→ Binance deposit (KYC
|
|
276
|
+
point)".
|
|
277
|
+
|
|
278
|
+
### First-funder (deanonymization)
|
|
279
|
+
|
|
280
|
+
`funder` follows a wallet's funding link backward: whoever sent its first gas,
|
|
281
|
+
then whoever funded that funder. A fresh laundering wallet must be bootstrapped
|
|
282
|
+
from somewhere, and the chain frequently terminates at an exchange withdrawal —
|
|
283
|
+
an identification point. A core primitive for tying "unrelated" wallets to one
|
|
284
|
+
controller. (Uses external transactions; wallets first funded by an internal
|
|
285
|
+
contract call need internal-tx data — a planned extension.)
|
|
286
|
+
|
|
287
|
+
### Victim lists and timing analysis
|
|
288
|
+
|
|
289
|
+
Two things a graph shows but can't hand you as evidence.
|
|
290
|
+
|
|
291
|
+
`victims` walks the money backwards from a consolidation wallet and lists every
|
|
292
|
+
address that fed it — amounts, transaction counts, first/last seen, explorer
|
|
293
|
+
links — and writes it to **CSV**. After a mass drain that list *is* the set of
|
|
294
|
+
victims, in the form an exchange's compliance team or a police report can
|
|
295
|
+
actually use.
|
|
296
|
+
|
|
297
|
+
`timeline` answers *when*. It buckets activity into a histogram and finds the
|
|
298
|
+
tightest window containing most of the transfers. This separates a theft from
|
|
299
|
+
ordinary wallet use: hundreds of transfers inside minutes is an automated tool
|
|
300
|
+
spending keys it already holds, whereas a real owner's activity is spread over
|
|
301
|
+
months. The tool says so in plain language rather than leaving you to eyeball it.
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
crypttrace victims bc1qADDRESS --chain btc --depth 2 -o victims.csv
|
|
305
|
+
crypttrace timeline bc1qADDRESS --chain btc --buckets 24
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+

|
|
309
|
+
|
|
310
|
+
A worked example — 1,169 swept addresses, 1,082.58 BTC, with the commands to
|
|
311
|
+
reproduce it — is in [`cases/coldcard-2026`](cases/coldcard-2026).
|
|
312
|
+
|
|
313
|
+
**Dust is filtered by default.** Addresses that become publicly known get spammed
|
|
314
|
+
with tiny transfers, which otherwise bury the transactions that matter and can
|
|
315
|
+
flip the timing verdict entirely. Each chain has a dust threshold; override with
|
|
316
|
+
`--min-value`, or keep everything with `--include-dust`. Bitcoin history is
|
|
317
|
+
paged rather than read one page deep, so an old sweep isn't hidden behind recent
|
|
318
|
+
spam.
|
|
319
|
+
|
|
320
|
+
### Bitcoin clustering (common-input-ownership)
|
|
321
|
+
|
|
322
|
+
Bitcoin's UTXO model enables the strongest clustering heuristic in blockchain
|
|
323
|
+
forensics: if several addresses sign the inputs of one transaction, one party
|
|
324
|
+
almost certainly controls all of them. `cluster` surfaces those co-signers,
|
|
325
|
+
turning a single address into a set of wallets belonging to the same owner. A
|
|
326
|
+
strong lead, not proof.
|
|
327
|
+
|
|
328
|
+
### Cross-chain tracing (bridges)
|
|
329
|
+
|
|
330
|
+
When funds cross a bridge the trail ends at the bridge contract and reappears on
|
|
331
|
+
another chain, with no free deterministic link between the two. `crosschain`
|
|
332
|
+
uses a behavioural heuristic that catches many real cases: launderers frequently
|
|
333
|
+
bridge to the *same address* they control on the destination chain, so after a
|
|
334
|
+
transfer into a known bridge the tool searches every other supported chain for
|
|
335
|
+
an inbound transfer to that address of a similar amount (bridges take a fee)
|
|
336
|
+
within a time window. Matches are reported with amount and delay — a strong
|
|
337
|
+
**lead, not proof**. Recognised bridges: Wormhole, the canonical Optimism /
|
|
338
|
+
Arbitrum / Base bridges, Across, Synapse and Celer cBridge (extend the
|
|
339
|
+
`"bridge"` entries in `labels/known.json`).
|
|
340
|
+
|
|
341
|
+
### Assets & USD values
|
|
342
|
+
|
|
343
|
+
The tracer follows the chain's native coin by default. `--asset` switches to a
|
|
344
|
+
token — most thefts and scams move stablecoins, so this is usually what you
|
|
345
|
+
want:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
crypttrace trace 0xADDRESS --asset usdt # USDT on Ethereum
|
|
349
|
+
crypttrace trace TADDRESS --chain tron --asset usdt # USDT-TRC20 on Tron
|
|
350
|
+
crypttrace tokens TADDRESS --chain tron
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Token contracts differ per chain, so the registry is chain-aware: `usdt` on Tron
|
|
354
|
+
resolves to `TR7NHqje…jLj6t`, on Ethereum to `0xdac17f95…31ec7`. Known symbols
|
|
355
|
+
are `usdt`, `usdc`, `dai`, `weth`, `wbtc` on EVM and `usdt`, `usdc` on
|
|
356
|
+
Tron/Solana; you can also pass any contract address directly.
|
|
357
|
+
|
|
358
|
+
Matching is by **exact contract**, which matters: attackers routinely airdrop
|
|
359
|
+
fake tokens named "USDT" to poison wallets, and those are ignored rather than
|
|
360
|
+
traced. Native coins and tokens are also kept strictly separate — summing TRX
|
|
361
|
+
with USDT would be meaningless.
|
|
362
|
+
|
|
363
|
+
Amounts carry approximate USD values (stablecoins pinned to $1, others priced
|
|
364
|
+
via CoinGecko). If pricing is unavailable, USD shows as `—` rather than a guess.
|
|
365
|
+
|
|
366
|
+
### Labels
|
|
367
|
+
|
|
368
|
+
Ships a curated seed set (major exchanges, Tornado Cash, bridges, notable
|
|
369
|
+
hacks). `update-labels` pulls authoritative public lists — currently the OFAC
|
|
370
|
+
SDN sanctioned-address list — and merges them into the local DB in
|
|
371
|
+
`~/.crypttrace/`. The curated seed wins on conflicts, so richer names survive.
|
|
372
|
+
Add sources in `labels/labels.py` → `SOURCES`.
|
|
373
|
+
|
|
374
|
+
Bitcoin, Tron and Solana coverage is currently Binance only: its reserve
|
|
375
|
+
wallets, taken from the address list Binance publishes itself for
|
|
376
|
+
proof-of-reserves. So `offramp` can flag Binance deposit addresses on those
|
|
377
|
+
chains, but funds sent to other exchanges there will still show as `unknown`.
|
|
378
|
+
Every label records where it came from — see `crypttrace labels audit`.
|
|
379
|
+
|
|
380
|
+
### Reports
|
|
381
|
+
|
|
382
|
+
`report` runs the full analysis and writes to `~/crypttrace-reports/` (override
|
|
383
|
+
with `--out`): a readable Markdown report (assessment, summary, key findings,
|
|
384
|
+
counterparties, the full fund-flow trace, methodology note) plus a `.json` with
|
|
385
|
+
the raw structured data.
|
|
386
|
+
|
|
387
|
+
### Self-verification
|
|
388
|
+
|
|
389
|
+
A forensics tool that quietly miscounts is worse than no tool: the output looks
|
|
390
|
+
authoritative and ends up in reports. `verify` re-derives the totals from what
|
|
391
|
+
the tool parsed and compares them against the figures the chain index reports
|
|
392
|
+
independently.
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
crypttrace verify bc1qADDRESS --chain btc
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
It reports one of:
|
|
399
|
+
|
|
400
|
+
| Status | Meaning |
|
|
401
|
+
|---|---|
|
|
402
|
+
| **verified** | Gross totals match the chain independently. |
|
|
403
|
+
| **consistent** | Balance reconciles, but this chain publishes no independent gross totals, so attribution is only partly checked. |
|
|
404
|
+
| **partial** | Only part of the address's history was read — totals are a floor, not the whole picture. |
|
|
405
|
+
| **mismatch** | Computed totals disagree with the chain. Don't rely on the output until it's explained. |
|
|
406
|
+
|
|
407
|
+
`victims` runs this automatically, because that list is the output most likely
|
|
408
|
+
to be quoted as evidence; the web UI shows the same status as a badge.
|
|
409
|
+
|
|
410
|
+
This exists because the tool *did* once miscount — attributing a whole
|
|
411
|
+
multi-input Bitcoin transaction to its first input address, crediting one
|
|
412
|
+
wallet with 89 BTC it never handled. That was caught by hand against a block
|
|
413
|
+
explorer; the check now catches it automatically.
|
|
414
|
+
|
|
415
|
+
### Caching & rate limits
|
|
416
|
+
|
|
417
|
+
Every API response is cached in SQLite under `~/.crypttrace/`, so re-running a
|
|
418
|
+
trace — or revisiting an address within one trace — costs no requests. Non-EVM
|
|
419
|
+
fetchers additionally throttle per host and retry with exponential backoff on
|
|
420
|
+
HTTP 429, so free endpoints degrade gracefully instead of erroring out.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Honest limitations
|
|
425
|
+
|
|
426
|
+
Read this before relying on the tool — and before promising anything to a
|
|
427
|
+
victim.
|
|
428
|
+
|
|
429
|
+
- **Pseudonymity.** You see that funds landed on `0xABC`, not who owns it. The
|
|
430
|
+
tool brings a trail to a *point of identification* (usually an exchange with
|
|
431
|
+
KYC). The name comes from a legal request to that exchange, not from the chain.
|
|
432
|
+
- **Mixers break the trail.** Tornado Cash and privacy pools sever the on-chain
|
|
433
|
+
link. Anything past them is heuristic and not guaranteed.
|
|
434
|
+
- **Heuristics are leads, not proof.** Off-ramp detection, cross-chain matching
|
|
435
|
+
and Bitcoin clustering are strong signals that can coincide by chance. Verify
|
|
436
|
+
before acting on them.
|
|
437
|
+
- **Labels are only as good as the database.** Unlabelled ≠ innocent.
|
|
438
|
+
- **Recovery depends on others.** crypttrace can tell you *when* and *where* to
|
|
439
|
+
act; freezing funds depends on exchanges and law enforcement responding.
|
|
440
|
+
|
|
441
|
+
## Layout
|
|
442
|
+
|
|
443
|
+
```
|
|
444
|
+
src/crypttrace/
|
|
445
|
+
cli.py # Typer CLI — every command
|
|
446
|
+
chains.py # unified multi-chain adapter (one transfer shape for all)
|
|
447
|
+
config.py # chains, keys, paths
|
|
448
|
+
fetchers/
|
|
449
|
+
etherscan.py # EVM (Etherscan v2) + SQLite cache
|
|
450
|
+
bitcoin.py # Bitcoin UTXO (mempool.space) + clustering
|
|
451
|
+
tron.py # Tron / TRC20 (TronGrid) + base58 conversion
|
|
452
|
+
solana.py # Solana JSON-RPC
|
|
453
|
+
http.py # shared cache, throttling, 429 backoff
|
|
454
|
+
labels/
|
|
455
|
+
known.json # curated label DB, one source per claim
|
|
456
|
+
partial.json # addresses known only in truncated form
|
|
457
|
+
labels.py # lookup, risk scoring, source imports
|
|
458
|
+
audit.py # checksum + provenance audit of the label DB
|
|
459
|
+
addresses.py # per-chain address validation (checksums)
|
|
460
|
+
store.py # local SQLite store of every transfer read
|
|
461
|
+
trace.py # fund-flow tree + graph builder
|
|
462
|
+
investigate.py # one-command victim workflow
|
|
463
|
+
analysis.py # victim lists, timeline, burst detection
|
|
464
|
+
assess.py # reasoned assessment with evidence and confidence
|
|
465
|
+
verify.py # reconcile computed totals against the chain
|
|
466
|
+
funder.py # first-funder heuristic
|
|
467
|
+
offramp.py # exchange-deposit detection
|
|
468
|
+
bridges.py # cross-chain matching
|
|
469
|
+
watch.py # watchlist + alerts
|
|
470
|
+
report.py # Markdown + JSON reports
|
|
471
|
+
prices.py # USD valuation
|
|
472
|
+
render.py # rich terminal rendering
|
|
473
|
+
webapp.py # Flask backend for the web UI
|
|
474
|
+
web/index.html # single-page frontend (interactive graph)
|
|
475
|
+
selftest.py # offline end-to-end checks, run by CI
|
|
476
|
+
cases/ # worked investigations with their data
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## Roadmap
|
|
480
|
+
|
|
481
|
+
- Exchange/service labels for Bitcoin, Tron and Solana
|
|
482
|
+
- `report --pdf` for exchange and law-enforcement filings
|
|
483
|
+
- Internal transactions (completes `funder` and contract-mediated transfers)
|
|
484
|
+
- Per-mint filtering for Solana SPL tokens
|
|
485
|
+
- More label sources: Chainabuse, CryptoScamDB, exchange deposit-address sets
|
|
486
|
+
- Spam/dust token filtering in `tokens`
|
|
487
|
+
- Entity clustering on EVM via the gas-funding heuristic
|
|
488
|
+
|
|
489
|
+
## Contributing
|
|
490
|
+
|
|
491
|
+
Label data is the highest-leverage contribution: exchange wallets, known scam
|
|
492
|
+
and drainer addresses, bridges. Add them to `labels/known.json` with a `chain`,
|
|
493
|
+
a `source` a reader can check and a `source_kind` (`self-published`,
|
|
494
|
+
`official-list`, `explorer-tag`, `research`, `community`), then run
|
|
495
|
+
`crypttrace labels audit` and `python selftest.py` before opening a PR.
|
|
496
|
+
Accuracy matters more than volume — a wrong label is worse than no label.
|
|
497
|
+
|
|
498
|
+
## Licence
|
|
499
|
+
|
|
500
|
+
MIT — see [LICENSE](LICENSE).
|
|
501
|
+
|
|
502
|
+
*Use responsibly. This is an investigative aid, not evidence of wrongdoing, and
|
|
503
|
+
not a substitute for law enforcement.*
|