crypttrace 0.31.0__tar.gz → 0.32.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.31.0/src/crypttrace.egg-info → crypttrace-0.32.0}/PKG-INFO +6 -1
- {crypttrace-0.31.0 → crypttrace-0.32.0}/README.md +5 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/__init__.py +1 -1
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/cli.py +19 -0
- crypttrace-0.32.0/src/crypttrace/export.py +94 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/web/index.html +20 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/webapp.py +8 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0/src/crypttrace.egg-info}/PKG-INFO +6 -1
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace.egg-info/SOURCES.txt +1 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/LICENSE +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/pyproject.toml +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/setup.cfg +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/addresses.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/analysis.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/assess.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/assets.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/bridges.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/chains.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/config.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/drain.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/evidence.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/fetchers/__init__.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/fetchers/bitcoin.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/fetchers/etherscan.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/fetchers/http.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/fetchers/solana.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/fetchers/ton.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/fetchers/tron.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/freeze.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/funder.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/gascluster.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/investigate.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/labels/__init__.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/labels/audit.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/labels/bulk.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/labels/known.json +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/labels/labels.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/labels/names.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/labels/partial.json +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/labels/tonapi.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/labels/tronscan.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/offramp.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/poisoning.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/prices.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/render.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/report.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/spam.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/store.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/trace.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/verify.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace/watch.py +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace.egg-info/dependency_links.txt +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace.egg-info/entry_points.txt +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.0}/src/crypttrace.egg-info/requires.txt +0 -0
- {crypttrace-0.31.0 → crypttrace-0.32.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.32.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
|
|
@@ -255,6 +255,10 @@ crypttrace trace 0xADDRESS --direction in
|
|
|
255
255
|
# Trace a token rather than the native coin (most thefts are stablecoins)
|
|
256
256
|
crypttrace trace 0xADDRESS --asset usdt --depth 4
|
|
257
257
|
|
|
258
|
+
# Save the traced graph for Gephi / yEd / Cytoscape, or for Maltego / Neo4j
|
|
259
|
+
crypttrace trace 0xADDRESS --depth 4 --export case.graphml
|
|
260
|
+
crypttrace trace 0xADDRESS --depth 4 --export case.csv
|
|
261
|
+
|
|
258
262
|
# Token holdings, valued in USD
|
|
259
263
|
crypttrace tokens 0xADDRESS
|
|
260
264
|
|
|
@@ -724,6 +728,7 @@ src/crypttrace/
|
|
|
724
728
|
bridges.py # cross-chain matching
|
|
725
729
|
watch.py # watchlist + alerts
|
|
726
730
|
report.py # Markdown + JSON reports
|
|
731
|
+
export.py # traced graph as GraphML (Gephi) and edge-list CSV (Maltego)
|
|
727
732
|
prices.py # USD valuation
|
|
728
733
|
render.py # rich terminal rendering
|
|
729
734
|
webapp.py # Flask backend for the web UI
|
|
@@ -221,6 +221,10 @@ crypttrace trace 0xADDRESS --direction in
|
|
|
221
221
|
# Trace a token rather than the native coin (most thefts are stablecoins)
|
|
222
222
|
crypttrace trace 0xADDRESS --asset usdt --depth 4
|
|
223
223
|
|
|
224
|
+
# Save the traced graph for Gephi / yEd / Cytoscape, or for Maltego / Neo4j
|
|
225
|
+
crypttrace trace 0xADDRESS --depth 4 --export case.graphml
|
|
226
|
+
crypttrace trace 0xADDRESS --depth 4 --export case.csv
|
|
227
|
+
|
|
224
228
|
# Token holdings, valued in USD
|
|
225
229
|
crypttrace tokens 0xADDRESS
|
|
226
230
|
|
|
@@ -690,6 +694,7 @@ src/crypttrace/
|
|
|
690
694
|
bridges.py # cross-chain matching
|
|
691
695
|
watch.py # watchlist + alerts
|
|
692
696
|
report.py # Markdown + JSON reports
|
|
697
|
+
export.py # traced graph as GraphML (Gephi) and edge-list CSV (Maltego)
|
|
693
698
|
prices.py # USD valuation
|
|
694
699
|
render.py # rich terminal rendering
|
|
695
700
|
webapp.py # Flask backend for the web UI
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""crypttrace — OSINT crypto investigation CLI."""
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.32.0"
|
|
@@ -222,6 +222,9 @@ def trace(
|
|
|
222
222
|
help="Top-N outflows to follow per address"),
|
|
223
223
|
direction: str = typer.Option("out", "--direction", "-D",
|
|
224
224
|
help="'out' = where funds went, 'in' = where they came from"),
|
|
225
|
+
export: Optional[Path] = typer.Option(None, "--export", "-o",
|
|
226
|
+
help="Also save the graph: .graphml (Gephi, yEd, Cytoscape) "
|
|
227
|
+
"or .csv (Maltego, Neo4j, spreadsheets)"),
|
|
225
228
|
):
|
|
226
229
|
"""Trace where funds moved, hop by hop, as a coloured tree (ETH or a token)."""
|
|
227
230
|
try:
|
|
@@ -232,6 +235,10 @@ def trace(
|
|
|
232
235
|
if direction not in ("out", "in"):
|
|
233
236
|
console.print("[red]Error:[/red] --direction must be 'out' or 'in'")
|
|
234
237
|
raise typer.Exit(1)
|
|
238
|
+
fmt = export.suffix.lower().lstrip(".") if export else None
|
|
239
|
+
if export and fmt not in ("graphml", "csv"):
|
|
240
|
+
console.print("[red]Error:[/red] --export takes a .graphml or .csv file name")
|
|
241
|
+
raise typer.Exit(1)
|
|
235
242
|
try:
|
|
236
243
|
tree = trace_mod.build_tree(address, chain, depth, branching, asset_desc, direction)
|
|
237
244
|
except (chains_mod.ChainError, etherscan.EtherscanError) as e:
|
|
@@ -242,6 +249,18 @@ def trace(
|
|
|
242
249
|
"\n[dim]Legend: \U0001F7E2 exchange \U0001F7E3 mixer \U0001F534 sanctioned/scam"
|
|
243
250
|
" \U0001F309 bridge \U0001F504 DEX \U0001F537 service ⚪ unknown[/dim]"
|
|
244
251
|
)
|
|
252
|
+
if export:
|
|
253
|
+
from crypttrace import export as export_mod
|
|
254
|
+
try:
|
|
255
|
+
graph = trace_mod.build_graph(address, chain, depth, branching, asset_desc, direction)
|
|
256
|
+
except (chains_mod.ChainError, etherscan.EtherscanError) as e:
|
|
257
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
258
|
+
raise typer.Exit(1)
|
|
259
|
+
export.write_text(export_mod.render(graph, chain, fmt), encoding="utf-8", newline="")
|
|
260
|
+
console.print(f"\n[green]Graph saved:[/green] {export} — {len(graph['nodes'])} addresses, "
|
|
261
|
+
f"{len(graph['edges'])} transfers"
|
|
262
|
+
+ (" [dim](Maltego: Import Graph from Table, map from/to to an address entity)[/dim]"
|
|
263
|
+
if fmt == "csv" else " [dim](open in Gephi, yEd or Cytoscape)[/dim]"))
|
|
245
264
|
|
|
246
265
|
|
|
247
266
|
@app.command()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Export a traced fund-flow graph for the tools analysts already use.
|
|
2
|
+
|
|
3
|
+
GraphML — Gephi, yEd, Cytoscape: nodes keep their label, kind, hop and
|
|
4
|
+
explorer link; edges their amount, USD value, transfer count and
|
|
5
|
+
exchange memo, so a layout can be coloured and sized by them.
|
|
6
|
+
CSV — one row per edge with both ends' labels and kinds: Maltego's
|
|
7
|
+
"Import Graph from Table" (map `from` and `to` to a
|
|
8
|
+
cryptocurrency-address entity), Neo4j's LOAD CSV, a spreadsheet.
|
|
9
|
+
|
|
10
|
+
Both take the dict `trace.build_graph` returns, the same graph the web UI draws.
|
|
11
|
+
"""
|
|
12
|
+
import csv
|
|
13
|
+
import io
|
|
14
|
+
from xml.sax.saxutils import escape, quoteattr
|
|
15
|
+
|
|
16
|
+
from crypttrace import chains
|
|
17
|
+
|
|
18
|
+
NODE_KEYS = [("label", "string"), ("kind", "string"), ("hop", "int"), ("root", "boolean"),
|
|
19
|
+
("trail_ends", "boolean"), ("chain", "string"), ("explorer", "string")]
|
|
20
|
+
EDGE_KEYS = [("amount", "double"), ("symbol", "string"), ("usd", "double"), ("transfers", "int"),
|
|
21
|
+
("memo", "string")]
|
|
22
|
+
CSV_HEAD = ["from", "from_label", "from_kind", "to", "to_label", "to_kind", "amount", "symbol",
|
|
23
|
+
"usd", "transfers", "memo", "chain", "from_explorer", "to_explorer"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _node_attrs(n: dict, chain: str) -> dict:
|
|
27
|
+
return {"label": n.get("label") or n.get("short") or n["id"], "kind": n.get("type") or "unknown",
|
|
28
|
+
"hop": n.get("level", 0), "root": bool(n.get("root")), "trail_ends": bool(n.get("terminal")),
|
|
29
|
+
"chain": chain, "explorer": chains.explorer_url(n["id"], chain)}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _edge_attrs(e: dict, symbol: str) -> dict:
|
|
33
|
+
return {"amount": e.get("value") or 0, "symbol": symbol, "usd": e.get("usd"),
|
|
34
|
+
"transfers": e.get("tx") or 0, "memo": e.get("memo") or ""}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _val(v, kind: str) -> str:
|
|
38
|
+
if v is None:
|
|
39
|
+
return ""
|
|
40
|
+
if kind == "boolean":
|
|
41
|
+
return "true" if v else "false"
|
|
42
|
+
return escape(str(v))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def to_graphml(graph: dict, chain: str) -> str:
|
|
46
|
+
"""GraphML 1.0, directed: money flows along the edges."""
|
|
47
|
+
symbol = graph.get("symbol", "")
|
|
48
|
+
out = ['<?xml version="1.0" encoding="UTF-8"?>',
|
|
49
|
+
'<graphml xmlns="http://graphml.graphdrawing.org/xmlns">']
|
|
50
|
+
for k, t in NODE_KEYS:
|
|
51
|
+
out.append(f' <key id="n_{k}" for="node" attr.name="{k}" attr.type="{t}"/>')
|
|
52
|
+
for k, t in EDGE_KEYS:
|
|
53
|
+
out.append(f' <key id="e_{k}" for="edge" attr.name="{k}" attr.type="{t}"/>')
|
|
54
|
+
out.append(f' <graph id={quoteattr("crypttrace " + chain)} edgedefault="directed">')
|
|
55
|
+
for n in graph.get("nodes", []):
|
|
56
|
+
out.append(f' <node id={quoteattr(n["id"])}>')
|
|
57
|
+
for (k, t), v in zip(NODE_KEYS, _node_attrs(n, chain).values()):
|
|
58
|
+
if v not in (None, ""):
|
|
59
|
+
out.append(f' <data key="n_{k}">{_val(v, t)}</data>')
|
|
60
|
+
out.append(" </node>")
|
|
61
|
+
for i, e in enumerate(graph.get("edges", [])):
|
|
62
|
+
out.append(f' <edge id="e{i}" source={quoteattr(e["from"])} target={quoteattr(e["to"])}>')
|
|
63
|
+
for (k, t), v in zip(EDGE_KEYS, _edge_attrs(e, symbol).values()):
|
|
64
|
+
if v not in (None, ""):
|
|
65
|
+
out.append(f' <data key="e_{k}">{_val(v, t)}</data>')
|
|
66
|
+
out.append(" </edge>")
|
|
67
|
+
out += [" </graph>", "</graphml>", ""]
|
|
68
|
+
return "\n".join(out)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def to_csv(graph: dict, chain: str) -> str:
|
|
72
|
+
"""One row per edge, both ends described — what a link-analysis import wants."""
|
|
73
|
+
symbol = graph.get("symbol", "")
|
|
74
|
+
nodes = {n["id"]: n for n in graph.get("nodes", [])}
|
|
75
|
+
buf = io.StringIO()
|
|
76
|
+
w = csv.writer(buf, lineterminator="\n")
|
|
77
|
+
w.writerow(CSV_HEAD)
|
|
78
|
+
for e in graph.get("edges", []):
|
|
79
|
+
a, b = nodes.get(e["from"], {"id": e["from"]}), nodes.get(e["to"], {"id": e["to"]})
|
|
80
|
+
ea = _edge_attrs(e, symbol)
|
|
81
|
+
w.writerow([e["from"], a.get("label") or "", a.get("type") or "unknown",
|
|
82
|
+
e["to"], b.get("label") or "", b.get("type") or "unknown",
|
|
83
|
+
ea["amount"], symbol, "" if ea["usd"] is None else ea["usd"], ea["transfers"],
|
|
84
|
+
ea["memo"], chain, chains.explorer_url(e["from"], chain), chains.explorer_url(e["to"], chain)])
|
|
85
|
+
return buf.getvalue()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
FORMATS = {"graphml": (to_graphml, "application/graphml+xml"), "csv": (to_csv, "text/csv")}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def render(graph: dict, chain: str, fmt: str) -> str:
|
|
92
|
+
if fmt not in FORMATS:
|
|
93
|
+
raise ValueError(f"unknown export format '{fmt}' — use one of: {', '.join(FORMATS)}")
|
|
94
|
+
return FORMATS[fmt][0](graph, chain)
|
|
@@ -230,6 +230,8 @@
|
|
|
230
230
|
<button id="go">Investigate</button>
|
|
231
231
|
<button id="fit" class="ghost" title="Fit graph to view">Fit</button>
|
|
232
232
|
<button id="casefile" class="ghost" title="Download a one-page case file for an exchange or the police">Case file</button>
|
|
233
|
+
<button id="exp-graphml" class="ghost" title="Download the graph for Gephi, yEd or Cytoscape">GraphML</button>
|
|
234
|
+
<button id="exp-csv" class="ghost" title="Download the graph as an edge list for Maltego, Neo4j or a spreadsheet">CSV</button>
|
|
233
235
|
</div>
|
|
234
236
|
|
|
235
237
|
<div class="legend">
|
|
@@ -779,6 +781,24 @@ async function downloadCaseFile(){
|
|
|
779
781
|
}
|
|
780
782
|
$('#casefile').addEventListener('click',downloadCaseFile);
|
|
781
783
|
|
|
784
|
+
// The traced graph as a file: GraphML for Gephi/yEd/Cytoscape, CSV for Maltego/Neo4j.
|
|
785
|
+
async function downloadGraph(fmt){
|
|
786
|
+
const a=$('#addr').value.trim(), b=$('#exp-'+fmt);
|
|
787
|
+
if(!a){ setStatus('enter an address',true); return; }
|
|
788
|
+
b.disabled=true; setStatus(`building the ${fmt} file…`,false,true);
|
|
789
|
+
try{
|
|
790
|
+
const r=await fetch(`/api/trace?address=${q(a)}&chain=${chain()}&depth=${$('#depth').value}&asset=${q($('#asset').value)}&direction=${$('#direction').value}&format=${fmt}`);
|
|
791
|
+
if(!r.ok){ const j=await r.json().catch(()=>({})); throw new Error(j.error||`HTTP ${r.status}`); }
|
|
792
|
+
const name=((r.headers.get('Content-Disposition')||'').match(/filename="([^"]+)"/)||[])[1]||`graph.${fmt}`;
|
|
793
|
+
const u=URL.createObjectURL(await r.blob()), l=document.createElement('a');
|
|
794
|
+
l.href=u; l.download=name; l.click(); URL.revokeObjectURL(u);
|
|
795
|
+
setStatus(`graph saved: ${name}`);
|
|
796
|
+
}catch(e){ setStatus(e.message,true); }
|
|
797
|
+
finally{ b.disabled=false; }
|
|
798
|
+
}
|
|
799
|
+
$('#exp-graphml').addEventListener('click',()=>downloadGraph('graphml'));
|
|
800
|
+
$('#exp-csv').addEventListener('click',()=>downloadGraph('csv'));
|
|
801
|
+
|
|
782
802
|
function downloadCsv(){
|
|
783
803
|
const head=['address','hop',`amount_sent_${victimSym}`,'transactions','first_seen_utc','last_seen_utc','label','explorer'];
|
|
784
804
|
const body=victimRows.map(r=>[r.address,r.hop,r.value,r.txs,dstamp(r.first_seen),dstamp(r.last_seen),
|
|
@@ -145,6 +145,14 @@ def create_app() -> Flask:
|
|
|
145
145
|
graph = trace_mod.build_graph(addr, chain, depth, branching, asset, direction)
|
|
146
146
|
except _KNOWN_ERRORS as e:
|
|
147
147
|
return jsonify({"error": str(e)}), 400
|
|
148
|
+
fmt = request.args.get("format", "")
|
|
149
|
+
if fmt: # the same graph as a file for Gephi / Maltego
|
|
150
|
+
from crypttrace import export as export_mod
|
|
151
|
+
if fmt not in export_mod.FORMATS:
|
|
152
|
+
return jsonify({"error": f"unknown format '{fmt}'"}), 400
|
|
153
|
+
name = f"crypttrace_{chain}_{addr[:12]}_{direction}.{fmt}"
|
|
154
|
+
return Response(export_mod.render(graph, chain, fmt), mimetype=export_mod.FORMATS[fmt][1],
|
|
155
|
+
headers={"Content-Disposition": f'attachment; filename="{name}"'})
|
|
148
156
|
return jsonify(graph)
|
|
149
157
|
|
|
150
158
|
@app.route("/api/assess")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crypttrace
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.32.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
|
|
@@ -255,6 +255,10 @@ crypttrace trace 0xADDRESS --direction in
|
|
|
255
255
|
# Trace a token rather than the native coin (most thefts are stablecoins)
|
|
256
256
|
crypttrace trace 0xADDRESS --asset usdt --depth 4
|
|
257
257
|
|
|
258
|
+
# Save the traced graph for Gephi / yEd / Cytoscape, or for Maltego / Neo4j
|
|
259
|
+
crypttrace trace 0xADDRESS --depth 4 --export case.graphml
|
|
260
|
+
crypttrace trace 0xADDRESS --depth 4 --export case.csv
|
|
261
|
+
|
|
258
262
|
# Token holdings, valued in USD
|
|
259
263
|
crypttrace tokens 0xADDRESS
|
|
260
264
|
|
|
@@ -724,6 +728,7 @@ src/crypttrace/
|
|
|
724
728
|
bridges.py # cross-chain matching
|
|
725
729
|
watch.py # watchlist + alerts
|
|
726
730
|
report.py # Markdown + JSON reports
|
|
731
|
+
export.py # traced graph as GraphML (Gephi) and edge-list CSV (Maltego)
|
|
727
732
|
prices.py # USD valuation
|
|
728
733
|
render.py # rich terminal rendering
|
|
729
734
|
webapp.py # Flask backend for the web UI
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|