crypttrace 0.17.0__tar.gz → 0.18.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.
Files changed (53) hide show
  1. {crypttrace-0.17.0/src/crypttrace.egg-info → crypttrace-0.18.0}/PKG-INFO +9 -1
  2. {crypttrace-0.17.0 → crypttrace-0.18.0}/README.md +8 -0
  3. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/__init__.py +1 -1
  4. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/labels/labels.py +7 -3
  5. crypttrace-0.18.0/src/crypttrace/labels/names.py +8 -0
  6. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/labels/tonapi.py +2 -7
  7. crypttrace-0.18.0/src/crypttrace/labels/tronscan.py +105 -0
  8. {crypttrace-0.17.0 → crypttrace-0.18.0/src/crypttrace.egg-info}/PKG-INFO +9 -1
  9. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace.egg-info/SOURCES.txt +2 -0
  10. {crypttrace-0.17.0 → crypttrace-0.18.0}/LICENSE +0 -0
  11. {crypttrace-0.17.0 → crypttrace-0.18.0}/pyproject.toml +0 -0
  12. {crypttrace-0.17.0 → crypttrace-0.18.0}/setup.cfg +0 -0
  13. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/addresses.py +0 -0
  14. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/analysis.py +0 -0
  15. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/assess.py +0 -0
  16. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/assets.py +0 -0
  17. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/bridges.py +0 -0
  18. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/chains.py +0 -0
  19. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/cli.py +0 -0
  20. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/config.py +0 -0
  21. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/drain.py +0 -0
  22. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/evidence.py +0 -0
  23. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/fetchers/__init__.py +0 -0
  24. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/fetchers/bitcoin.py +0 -0
  25. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/fetchers/etherscan.py +0 -0
  26. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/fetchers/http.py +0 -0
  27. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/fetchers/solana.py +0 -0
  28. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/fetchers/ton.py +0 -0
  29. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/fetchers/tron.py +0 -0
  30. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/freeze.py +0 -0
  31. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/funder.py +0 -0
  32. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/investigate.py +0 -0
  33. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/labels/__init__.py +0 -0
  34. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/labels/audit.py +0 -0
  35. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/labels/bulk.py +0 -0
  36. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/labels/known.json +0 -0
  37. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/labels/partial.json +0 -0
  38. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/offramp.py +0 -0
  39. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/poisoning.py +0 -0
  40. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/prices.py +0 -0
  41. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/render.py +0 -0
  42. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/report.py +0 -0
  43. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/spam.py +0 -0
  44. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/store.py +0 -0
  45. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/trace.py +0 -0
  46. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/verify.py +0 -0
  47. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/watch.py +0 -0
  48. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/web/index.html +0 -0
  49. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace/webapp.py +0 -0
  50. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace.egg-info/dependency_links.txt +0 -0
  51. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace.egg-info/entry_points.txt +0 -0
  52. {crypttrace-0.17.0 → crypttrace-0.18.0}/src/crypttrace.egg-info/requires.txt +0 -0
  53. {crypttrace-0.17.0 → crypttrace-0.18.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.17.0
3
+ Version: 0.18.0
4
4
  Summary: OSINT toolkit for crypto investigations: trace stolen funds across Ethereum, Bitcoin, Tron, Solana and TON
5
5
  Author: bobslayerX
6
6
  License-Expression: MIT
@@ -104,6 +104,7 @@ export TRONGRID_API_KEY=xxxx # optional: higher TronGrid quota
104
104
  export CRYPTTRACE_SOLANA_RPC=https://… # optional: your own (faster) Solana RPC
105
105
  export TONCENTER_API_KEY=xxxx # optional: toncenter allows ~1 request/s without it
106
106
  export TONAPI_KEY=xxxx # optional: faster tonapi name lookups on TON
107
+ export TRONSCAN_API_KEY=xxxx # optional: Tron exchange and scam tags from Tronscan
107
108
  ```
108
109
 
109
110
  (In PowerShell: `$env:TRONGRID_API_KEY = "xxxx"` and so on.)
@@ -537,6 +538,13 @@ accounts. These become labels of kind `explorer-tag`, cached for a week. A
537
538
  "kucoin-1.ton" — is not taken as a label. A trace stops at a DEX: the funds
538
539
  come back to the sender in another token.
539
540
 
541
+ **Tron: tags from Tronscan.** With a free Tronscan API key (`TRONSCAN_API_KEY`,
542
+ created in a Tronscan account), Tron addresses the curated set does not know
543
+ get Tronscan's tags: exchange wallets ("Binance-Cold 1", "Okex 1") become
544
+ exchange labels and a red tag (scam, phishing) makes the address a scam — both
545
+ as `explorer-tag` labels naming Tronscan as the source, cached for a week.
546
+ Without a key nothing is fetched.
547
+
540
548
  **Exchange deposits with a memo.** A TON exchange takes all customers' deposits
541
549
  into one hot wallet and tells them apart by the transfer comment, the *memo*.
542
550
  The memo is the lead: it names one account at the exchange, which holds that
@@ -70,6 +70,7 @@ export TRONGRID_API_KEY=xxxx # optional: higher TronGrid quota
70
70
  export CRYPTTRACE_SOLANA_RPC=https://… # optional: your own (faster) Solana RPC
71
71
  export TONCENTER_API_KEY=xxxx # optional: toncenter allows ~1 request/s without it
72
72
  export TONAPI_KEY=xxxx # optional: faster tonapi name lookups on TON
73
+ export TRONSCAN_API_KEY=xxxx # optional: Tron exchange and scam tags from Tronscan
73
74
  ```
74
75
 
75
76
  (In PowerShell: `$env:TRONGRID_API_KEY = "xxxx"` and so on.)
@@ -503,6 +504,13 @@ accounts. These become labels of kind `explorer-tag`, cached for a week. A
503
504
  "kucoin-1.ton" — is not taken as a label. A trace stops at a DEX: the funds
504
505
  come back to the sender in another token.
505
506
 
507
+ **Tron: tags from Tronscan.** With a free Tronscan API key (`TRONSCAN_API_KEY`,
508
+ created in a Tronscan account), Tron addresses the curated set does not know
509
+ get Tronscan's tags: exchange wallets ("Binance-Cold 1", "Okex 1") become
510
+ exchange labels and a red tag (scam, phishing) makes the address a scam — both
511
+ as `explorer-tag` labels naming Tronscan as the source, cached for a week.
512
+ Without a key nothing is fetched.
513
+
506
514
  **Exchange deposits with a memo.** A TON exchange takes all customers' deposits
507
515
  into one hot wallet and tells them apart by the transfer comment, the *memo*.
508
516
  The memo is the lead: it names one account at the exchange, which holds that
@@ -1,2 +1,2 @@
1
1
  """crypttrace — OSINT crypto investigation CLI."""
2
- __version__ = "0.17.0"
2
+ __version__ = "0.18.0"
@@ -17,7 +17,7 @@ from typing import Optional, Dict, List
17
17
  import requests
18
18
 
19
19
  from crypttrace import addresses, config
20
- from crypttrace.labels import bulk, tonapi
20
+ from crypttrace.labels import bulk, tonapi, tronscan
21
21
 
22
22
  _HERE = Path(__file__).parent
23
23
  _IMPORTED = config.DATA_DIR / "imported_labels.json"
@@ -189,15 +189,19 @@ def lookup(address: str) -> Optional[dict]:
189
189
  found = _match_partial(address) or bulk.lookup(address)
190
190
  if not found and (len(address) == 48 or ":" in address):
191
191
  found = tonapi.lookup(address) # cached explorer tag, no network here
192
+ elif not found and len(address) == 34 and address.startswith("T"):
193
+ found = tronscan.lookup(address)
192
194
  return found
193
195
 
194
196
 
195
197
  def enrich(addrs, chain: str) -> None:
196
- """Fetch explorer tags the curated set lacks (TON: tonapi names). Network, cached."""
198
+ """Fetch explorer tags the curated set lacks (TON: tonapi, Tron: Tronscan). Network, cached."""
199
+ _load()
197
200
  if chain == "ton":
198
- _load()
199
201
  tonapi.enrich(a for a in addrs
200
202
  if a and not _KNOWN.get((addresses.ton_friendly(a) or a).lower()))
203
+ elif chain == "tron":
204
+ tronscan.enrich(a for a in addrs if a and not _KNOWN.get(a.lower()))
201
205
 
202
206
 
203
207
  def is_deposit(address: str) -> bool:
@@ -0,0 +1,8 @@
1
+ """Exchange names as explorers spell them — shared by the explorer-tag sources."""
2
+ import re
3
+
4
+ # custodial platforms that hold KYC and can freeze: a deposit there is a lead
5
+ EXCHANGES = re.compile(
6
+ r"\b(binance|bybit|okx|okex|kucoin|htx|huobi|gate(\.io)?|mexc|bitget|kraken|coinbase|"
7
+ r"bitfinex|bingx|bitmart|lbank|whitebit|exmo|coinex|poloniex|crypto\.com|bitpanda|"
8
+ r"bithumb|upbit|cryptobot|crypto bot|xrocket|wallet in telegram|telegram wallet)\b", re.I)
@@ -26,6 +26,7 @@ import requests
26
26
 
27
27
  from crypttrace import addresses, config
28
28
  from crypttrace.fetchers import http
29
+ from crypttrace.labels.names import EXCHANGES
29
30
 
30
31
  API = "https://tonapi.io/v2/accounts/{}"
31
32
  CACHE = config.DATA_DIR / "tonapi_names.json"
@@ -33,12 +34,6 @@ MAX_AGE = 7 * 24 * 3600
33
34
  _KEY = os.environ.get("TONAPI_KEY")
34
35
  http.MIN_INTERVAL["tonapi.io"] = 0.12 if _KEY else 1.1
35
36
 
36
- # custodial platforms that hold KYC and can freeze: a deposit there is a lead
37
- _EXCHANGES = re.compile(
38
- r"\b(binance|bybit|okx|okex|kucoin|htx|huobi|gate(\.io)?|mexc|bitget|kraken|coinbase|"
39
- r"bitfinex|bingx|bitmart|lbank|whitebit|exmo|coinex|poloniex|crypto\.com|bitpanda|"
40
- r"bithumb|upbit|cryptobot|crypto bot|xrocket|wallet in telegram|telegram wallet)\b", re.I)
41
-
42
37
  _cache: Optional[Dict[str, dict]] = None
43
38
 
44
39
 
@@ -73,7 +68,7 @@ def classify(account: dict) -> Optional[dict]:
73
68
  return {"name": name or "DEX contract", "type": "dex"}
74
69
  if not name or self_named:
75
70
  return None
76
- if _EXCHANGES.search(name):
71
+ if EXCHANGES.search(name):
77
72
  memo = " (memo required)" if account.get("memo_required") else ""
78
73
  return {"name": f"{name}{memo}", "type": "exchange", "memo_required": bool(account.get("memo_required"))}
79
74
  return {"name": name, "type": "service"}
@@ -0,0 +1,105 @@
1
+ """Tron address tags from Tronscan — exchange wallets and risk warnings.
2
+
3
+ Tronscan tags exchange hot and cold wallets ("Binance-Cold 1", "OKX-Hot") and
4
+ puts red tags on addresses it flags as scams or phishing. Both become labels
5
+ of kind "explorer-tag", with Tronscan named as the source; a red tag makes the
6
+ address a scam. Grey tags and user feedback are not enough to call an address
7
+ anything.
8
+
9
+ Needs a free API key (TRONSCAN_API_KEY, created in a Tronscan account); without
10
+ one nothing is fetched and Tron labels come from the curated set alone.
11
+ Lookups are cached on disk for a week; only `enrich()` uses the network.
12
+ """
13
+ import datetime as dt
14
+ import json
15
+ import os
16
+ import sys
17
+ import time
18
+ from typing import Dict, Iterable, Optional
19
+
20
+ import requests
21
+
22
+ from crypttrace import config
23
+ from crypttrace.fetchers import http
24
+ from crypttrace.labels.names import EXCHANGES
25
+
26
+ API = "https://apilist.tronscanapi.com/api/accountv2"
27
+ CACHE = config.DATA_DIR / "tronscan_tags.json"
28
+ MAX_AGE = 7 * 24 * 3600
29
+ http.MIN_INTERVAL["apilist.tronscanapi.com"] = 0.25
30
+ _cache: Optional[Dict[str, dict]] = None
31
+
32
+
33
+ def _key() -> Optional[str]:
34
+ k = os.environ.get("TRONSCAN_API_KEY")
35
+ if not k and sys.platform == "win32": # set with [Environment]::SetEnvironmentVariable(..., "User")
36
+ try:
37
+ import winreg
38
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment") as reg:
39
+ k = winreg.QueryValueEx(reg, "TRONSCAN_API_KEY")[0]
40
+ except OSError:
41
+ k = None
42
+ return (k or "").strip() or None
43
+
44
+
45
+ def _load() -> Dict[str, dict]:
46
+ global _cache
47
+ if _cache is None:
48
+ try:
49
+ _cache = json.loads(CACHE.read_text(encoding="utf-8"))
50
+ except (OSError, ValueError):
51
+ _cache = {}
52
+ return _cache
53
+
54
+
55
+ def _save() -> None:
56
+ try:
57
+ CACHE.parent.mkdir(parents=True, exist_ok=True)
58
+ tmp = CACHE.with_suffix(".tmp")
59
+ tmp.write_text(json.dumps(_load(), ensure_ascii=False), encoding="utf-8")
60
+ tmp.replace(CACHE)
61
+ except OSError:
62
+ pass
63
+
64
+
65
+ def classify(account: dict) -> Optional[dict]:
66
+ """A label from one Tronscan account record, or None if it names nothing."""
67
+ red = (account.get("redTag") or "").strip()
68
+ tag = (account.get("publicTag") or account.get("addressTag") or "").strip()
69
+ if red:
70
+ return {"name": f"{tag or red} (Tronscan: {red})", "type": "scam"}
71
+ if not tag:
72
+ return None
73
+ if EXCHANGES.search(tag.replace("-", " ")):
74
+ return {"name": tag, "type": "exchange"}
75
+ return {"name": tag, "type": "service"}
76
+
77
+
78
+ def lookup(address: str) -> Optional[dict]:
79
+ """Cached Tronscan label, without touching the network."""
80
+ entry = _load().get(address)
81
+ if not entry or not entry.get("label"):
82
+ return None
83
+ return {**entry["label"], "chain": "tron", "source_kind": "explorer-tag",
84
+ "source": f"Tronscan address tag, fetched {entry.get('day', '')}"}
85
+
86
+
87
+ def enrich(addrs: Iterable[str]) -> None:
88
+ """Fetch Tronscan tags for addresses not in the cache (or stale). No key, no fetch."""
89
+ key = _key()
90
+ if not key:
91
+ return
92
+ cache, now, changed = _load(), time.time(), False
93
+ for a in dict.fromkeys(addrs):
94
+ if not a or not a.startswith("T") or len(a) != 34 or now - cache.get(a, {}).get("at", 0) < MAX_AGE:
95
+ continue
96
+ try:
97
+ rec = http.request_json(API, {"address": a}, timeout=20, headers={"TRON-PRO-API-KEY": key})
98
+ except (http.RateLimited, requests.RequestException, ValueError):
99
+ continue
100
+ if not isinstance(rec, dict) or "__status__" in rec:
101
+ continue
102
+ cache[a] = {"at": now, "day": dt.date.today().isoformat(), "label": classify(rec)}
103
+ changed = True
104
+ if changed:
105
+ _save()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crypttrace
3
- Version: 0.17.0
3
+ Version: 0.18.0
4
4
  Summary: OSINT toolkit for crypto investigations: trace stolen funds across Ethereum, Bitcoin, Tron, Solana and TON
5
5
  Author: bobslayerX
6
6
  License-Expression: MIT
@@ -104,6 +104,7 @@ export TRONGRID_API_KEY=xxxx # optional: higher TronGrid quota
104
104
  export CRYPTTRACE_SOLANA_RPC=https://… # optional: your own (faster) Solana RPC
105
105
  export TONCENTER_API_KEY=xxxx # optional: toncenter allows ~1 request/s without it
106
106
  export TONAPI_KEY=xxxx # optional: faster tonapi name lookups on TON
107
+ export TRONSCAN_API_KEY=xxxx # optional: Tron exchange and scam tags from Tronscan
107
108
  ```
108
109
 
109
110
  (In PowerShell: `$env:TRONGRID_API_KEY = "xxxx"` and so on.)
@@ -537,6 +538,13 @@ accounts. These become labels of kind `explorer-tag`, cached for a week. A
537
538
  "kucoin-1.ton" — is not taken as a label. A trace stops at a DEX: the funds
538
539
  come back to the sender in another token.
539
540
 
541
+ **Tron: tags from Tronscan.** With a free Tronscan API key (`TRONSCAN_API_KEY`,
542
+ created in a Tronscan account), Tron addresses the curated set does not know
543
+ get Tronscan's tags: exchange wallets ("Binance-Cold 1", "Okex 1") become
544
+ exchange labels and a red tag (scam, phishing) makes the address a scam — both
545
+ as `explorer-tag` labels naming Tronscan as the source, cached for a week.
546
+ Without a key nothing is fetched.
547
+
540
548
  **Exchange deposits with a memo.** A TON exchange takes all customers' deposits
541
549
  into one hot wallet and tells them apart by the transfer comment, the *memo*.
542
550
  The memo is the lead: it names one account at the exchange, which holds that
@@ -44,6 +44,8 @@ src/crypttrace/labels/audit.py
44
44
  src/crypttrace/labels/bulk.py
45
45
  src/crypttrace/labels/known.json
46
46
  src/crypttrace/labels/labels.py
47
+ src/crypttrace/labels/names.py
47
48
  src/crypttrace/labels/partial.json
48
49
  src/crypttrace/labels/tonapi.py
50
+ src/crypttrace/labels/tronscan.py
49
51
  src/crypttrace/web/index.html
File without changes
File without changes
File without changes