crypttrace 0.2.0__tar.gz → 0.3.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/src/crypttrace.egg-info → crypttrace-0.3.0}/PKG-INFO +20 -8
- {crypttrace-0.2.0 → crypttrace-0.3.0}/README.md +19 -7
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/__init__.py +1 -1
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/cli.py +4 -4
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/investigate.py +2 -11
- crypttrace-0.3.0/src/crypttrace/labels/known.json +284 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/labels/labels.py +10 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/offramp.py +33 -17
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/trace.py +5 -4
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/watch.py +2 -2
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/webapp.py +1 -2
- {crypttrace-0.2.0 → crypttrace-0.3.0/src/crypttrace.egg-info}/PKG-INFO +20 -8
- crypttrace-0.2.0/src/crypttrace/labels/known.json +0 -148
- {crypttrace-0.2.0 → crypttrace-0.3.0}/LICENSE +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/pyproject.toml +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/setup.cfg +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/addresses.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/analysis.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/assess.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/assets.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/bridges.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/chains.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/config.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/fetchers/__init__.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/fetchers/bitcoin.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/fetchers/etherscan.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/fetchers/http.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/fetchers/solana.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/fetchers/tron.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/funder.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/labels/__init__.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/labels/audit.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/labels/partial.json +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/prices.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/render.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/report.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/store.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/verify.py +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace/web/index.html +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace.egg-info/SOURCES.txt +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace.egg-info/dependency_links.txt +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace.egg-info/entry_points.txt +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace.egg-info/requires.txt +0 -0
- {crypttrace-0.2.0 → crypttrace-0.3.0}/src/crypttrace.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crypttrace
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: OSINT toolkit for crypto investigations: trace stolen funds across Ethereum, Bitcoin, Tron and Solana
|
|
5
5
|
Author: bobslayerX
|
|
6
6
|
License-Expression: MIT
|
|
@@ -273,7 +273,9 @@ behaviour: an address forwarding most of its outgoing value to a labelled
|
|
|
273
273
|
exchange is almost certainly a deposit address, i.e. the cash-out point where
|
|
274
274
|
that exchange holds the depositor's KYC. `trace` applies the same heuristic
|
|
275
275
|
automatically, turning an anonymous intermediary into "→ Binance deposit (KYC
|
|
276
|
-
point)".
|
|
276
|
+
point)". It works on every supported chain; on Tron and Solana, where deposits
|
|
277
|
+
are usually swept as stablecoins, it checks USDT and USDC as well as the native
|
|
278
|
+
coin.
|
|
277
279
|
|
|
278
280
|
### First-funder (deanonymization)
|
|
279
281
|
|
|
@@ -371,11 +373,12 @@ SDN sanctioned-address list — and merges them into the local DB in
|
|
|
371
373
|
`~/.crypttrace/`. The curated seed wins on conflicts, so richer names survive.
|
|
372
374
|
Add sources in `labels/labels.py` → `SOURCES`.
|
|
373
375
|
|
|
374
|
-
Bitcoin, Tron and Solana
|
|
375
|
-
|
|
376
|
-
proof-of-reserves.
|
|
377
|
-
|
|
378
|
-
Every label records where it came from — see
|
|
376
|
+
On Bitcoin, Tron and Solana the labels cover the reserve wallets of Binance,
|
|
377
|
+
OKX and HTX, plus Bybit's older 2022 list — each taken from the address list the
|
|
378
|
+
exchange publishes itself for proof-of-reserves. That is what lets `offramp`
|
|
379
|
+
flag deposit addresses on those chains. Funds sent to an exchange not on that
|
|
380
|
+
list still show as `unknown`. Every label records where it came from — see
|
|
381
|
+
`crypttrace labels audit`.
|
|
379
382
|
|
|
380
383
|
### Reports
|
|
381
384
|
|
|
@@ -478,7 +481,16 @@ cases/ # worked investigations with their data
|
|
|
478
481
|
|
|
479
482
|
## Roadmap
|
|
480
483
|
|
|
481
|
-
-
|
|
484
|
+
- More exchanges on Bitcoin, Tron and Solana (now: Binance, OKX, HTX, Bybit),
|
|
485
|
+
and refreshing these lists as the exchanges republish them
|
|
486
|
+
- OKX's full signed address set (~310k, mostly deposit addresses) as an
|
|
487
|
+
optional `update-labels` download, so deposits are labelled outright
|
|
488
|
+
- Address-poisoning detection: flag look-alike addresses (same first and last
|
|
489
|
+
characters) in a victim's history before they copy the wrong one
|
|
490
|
+
- Stablecoin freeze check: whether USDT/USDC at an address is already frozen
|
|
491
|
+
by the issuer, and who to ask for a freeze
|
|
492
|
+
- `report --html`: one self-contained file with the interactive graph, to
|
|
493
|
+
send to an exchange or attach to a police report
|
|
482
494
|
- `report --pdf` for exchange and law-enforcement filings
|
|
483
495
|
- Internal transactions (completes `funder` and contract-mediated transfers)
|
|
484
496
|
- Per-mint filtering for Solana SPL tokens
|
|
@@ -239,7 +239,9 @@ behaviour: an address forwarding most of its outgoing value to a labelled
|
|
|
239
239
|
exchange is almost certainly a deposit address, i.e. the cash-out point where
|
|
240
240
|
that exchange holds the depositor's KYC. `trace` applies the same heuristic
|
|
241
241
|
automatically, turning an anonymous intermediary into "→ Binance deposit (KYC
|
|
242
|
-
point)".
|
|
242
|
+
point)". It works on every supported chain; on Tron and Solana, where deposits
|
|
243
|
+
are usually swept as stablecoins, it checks USDT and USDC as well as the native
|
|
244
|
+
coin.
|
|
243
245
|
|
|
244
246
|
### First-funder (deanonymization)
|
|
245
247
|
|
|
@@ -337,11 +339,12 @@ SDN sanctioned-address list — and merges them into the local DB in
|
|
|
337
339
|
`~/.crypttrace/`. The curated seed wins on conflicts, so richer names survive.
|
|
338
340
|
Add sources in `labels/labels.py` → `SOURCES`.
|
|
339
341
|
|
|
340
|
-
Bitcoin, Tron and Solana
|
|
341
|
-
|
|
342
|
-
proof-of-reserves.
|
|
343
|
-
|
|
344
|
-
Every label records where it came from — see
|
|
342
|
+
On Bitcoin, Tron and Solana the labels cover the reserve wallets of Binance,
|
|
343
|
+
OKX and HTX, plus Bybit's older 2022 list — each taken from the address list the
|
|
344
|
+
exchange publishes itself for proof-of-reserves. That is what lets `offramp`
|
|
345
|
+
flag deposit addresses on those chains. Funds sent to an exchange not on that
|
|
346
|
+
list still show as `unknown`. Every label records where it came from — see
|
|
347
|
+
`crypttrace labels audit`.
|
|
345
348
|
|
|
346
349
|
### Reports
|
|
347
350
|
|
|
@@ -444,7 +447,16 @@ cases/ # worked investigations with their data
|
|
|
444
447
|
|
|
445
448
|
## Roadmap
|
|
446
449
|
|
|
447
|
-
-
|
|
450
|
+
- More exchanges on Bitcoin, Tron and Solana (now: Binance, OKX, HTX, Bybit),
|
|
451
|
+
and refreshing these lists as the exchanges republish them
|
|
452
|
+
- OKX's full signed address set (~310k, mostly deposit addresses) as an
|
|
453
|
+
optional `update-labels` download, so deposits are labelled outright
|
|
454
|
+
- Address-poisoning detection: flag look-alike addresses (same first and last
|
|
455
|
+
characters) in a victim's history before they copy the wrong one
|
|
456
|
+
- Stablecoin freeze check: whether USDT/USDC at an address is already frozen
|
|
457
|
+
by the issuer, and who to ask for a freeze
|
|
458
|
+
- `report --html`: one self-contained file with the interactive graph, to
|
|
459
|
+
send to an exchange or attach to a police report
|
|
448
460
|
- `report --pdf` for exchange and law-enforcement filings
|
|
449
461
|
- Internal transactions (completes `funder` and contract-mediated transfers)
|
|
450
462
|
- Per-mint filtering for Solana SPL tokens
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""crypttrace — OSINT crypto investigation CLI."""
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.3.0"
|
|
@@ -262,21 +262,21 @@ def crosschain(
|
|
|
262
262
|
|
|
263
263
|
@app.command()
|
|
264
264
|
def offramp(
|
|
265
|
-
address: str = typer.Argument(..., help="Address to check
|
|
265
|
+
address: str = typer.Argument(..., help="Address to check"),
|
|
266
266
|
chain: str = CHAIN_OPT,
|
|
267
267
|
):
|
|
268
268
|
"""Check whether an address is an exchange deposit address (cash-out / off-ramp)."""
|
|
269
269
|
try:
|
|
270
270
|
hit = offramp_mod.detect(address, chain)
|
|
271
|
-
except etherscan.EtherscanError as e:
|
|
271
|
+
except (chains_mod.ChainError, etherscan.EtherscanError) as e:
|
|
272
272
|
console.print(f"[red]Error:[/red] {e}")
|
|
273
273
|
raise typer.Exit(1)
|
|
274
274
|
if hit:
|
|
275
275
|
pct = int(hit["fraction"] * 100)
|
|
276
276
|
console.print(
|
|
277
277
|
f"[green]➜ Likely off-ramp:[/green] this address forwarded ~{pct}% of outgoing "
|
|
278
|
-
f"funds ({hit['forwarded']:.4f}) to [bold]{hit['exchange']}[/bold].\n"
|
|
279
|
-
f" It is probably a {hit['
|
|
278
|
+
f"funds ({hit['forwarded']:.4f} {hit['symbol']}) to [bold]{hit['exchange']}[/bold].\n"
|
|
279
|
+
f" It is probably a deposit address at {hit['company']} — a KYC identification point."
|
|
280
280
|
)
|
|
281
281
|
else:
|
|
282
282
|
console.print("[dim]No exchange-forwarding pattern detected. "
|
|
@@ -74,7 +74,7 @@ def analyse(address: str, chain: str = "eth", asset: Optional[dict] = None,
|
|
|
74
74
|
result["funding"] = []
|
|
75
75
|
|
|
76
76
|
try:
|
|
77
|
-
result["offramp"] = offramp_mod.detect(address, chain)
|
|
77
|
+
result["offramp"] = offramp_mod.detect(address, chain)
|
|
78
78
|
except Exception:
|
|
79
79
|
result["offramp"] = None
|
|
80
80
|
|
|
@@ -82,20 +82,11 @@ def analyse(address: str, chain: str = "eth", asset: Optional[dict] = None,
|
|
|
82
82
|
return result
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
def _service_name(label: str) -> str:
|
|
86
|
-
"""'Binance 14 (hot wallet)' -> 'Binance' — victims need the company, not our label."""
|
|
87
|
-
import re
|
|
88
|
-
name = re.sub(r"\(.*?\)", "", label) # drop parentheticals
|
|
89
|
-
name = name.split(" deposit")[0].split(":")[0]
|
|
90
|
-
name = re.sub(r"\s+\d+\s*$", "", name.strip()) # drop trailing wallet numbers
|
|
91
|
-
return name.strip() or label
|
|
92
|
-
|
|
93
|
-
|
|
94
85
|
def build_guidance(r: dict) -> dict:
|
|
95
86
|
"""Turn findings into plain-language next steps."""
|
|
96
87
|
steps: List[dict] = []
|
|
97
88
|
exchanges = r["exchanges"]
|
|
98
|
-
ex_names = sorted({
|
|
89
|
+
ex_names = sorted({labels.company(f["label"]) for f in exchanges}) if exchanges else []
|
|
99
90
|
|
|
100
91
|
if exchanges:
|
|
101
92
|
headline = ("Good news, relatively speaking: the trail reaches a "
|