Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion skills/etherscan-flow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ If you hit a false positive on Claude: report it via `/feedback`, and if you do

Every node and edge carries a `chainid`. `hop` counts transfers from the seed, so seed and scope addresses are `hop: 0`. Final edges are chronological by timestamp or block; receipt-log edges sharing one txhash are ordered by `log_index`; node coordinates place hop columns left-to-right with same-hop branches top-to-bottom by first-flow sequence. `_meta.layout` records the ordering and spacing that produced those coordinates. Repeated movements between the same pair collapse into one edge: `txcount` is how many transactions it merges, `txhash` is the earliest, and `txhashes` lists all of them so the canvas can validate each one on-chain. Anything the API did not resolve — a balance that was never needed, an unknown token symbol — is `null` rather than a guess. Financial totals live under `_meta.financials`; structured forensic conclusions live under `_meta.analysis` (`null` for ordinary cases). There are no top-level `financials` or `analysis` keys.

When a user edits Case Findings in Etherscan Flow, the canvas preserves the structured analysis and stores the displayed Markdown at `_meta.ui.findings_markdown`. The tracer itself does not emit this reserved UI field. Saved and exported layouts may also carry non-zero numeric `x` and `y` coordinates.
When a user edits Case Findings in Etherscan Flow, the canvas preserves the structured analysis and stores the displayed Markdown at `_meta.ui.findings_markdown`. The tracer itself does not emit this reserved UI field. Saved and exported layouts may also carry non-zero numeric `x` and `y` coordinates, and `"pinned": true` on a node the user locked in place (also canvas-only; the tracer never emits it).

The full contract is [`schema/case.schema.json`](./schema/case.schema.json) (JSON Schema), with a validating example in [`examples/`](./examples/). CI checks the example against the schema on every push, so the documented shape and a real case can't drift apart.

Expand Down
1 change: 1 addition & 0 deletions skills/etherscan-flow/references/output-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Node `id` values must be short unique alphanumeric strings (2–12 chars, e.g. `
- **`_meta.financials`** is where all financial totals live (Step 3B). There is no top-level `financials` key; the top level is exactly `id`, `name`, `schemaVersion`, `nodes`, `edges`, `_meta`.
- **`_meta.analysis`** is `null` for ordinary cases and a required structured forensic conclusion for security cases. `_meta.patterns` remains a compact index of detected patterns; it never replaces the evidence, confidence, competing hypotheses, and limitations in `_meta.analysis`.
- **`_meta.layout`** is emitted by the tracer's final ordering pass. It records the chronology strategy, same-transaction ordering, node ordering, direction, and coordinate spacing that produced the non-zero layout; it is separate from user-edited canvas state.
- **Node `pinned` is reserved for Etherscan Flow.** The tracer does not emit it. When a user pins a card on the canvas, the saved or exported case carries `"pinned": true` on that node, and the card keeps its `x`/`y` through dragging and Tidy Up. The field is absent when unpinned — never `false`.
- **`_meta.ui` is reserved for Etherscan Flow.** The tracer does not emit it. After a user edits Case Findings, Etherscan Flow stores the displayed Markdown at `_meta.ui.findings_markdown` while preserving `_meta.analysis`. This user-authored Markdown may contain newlines and exceed the tracer's 200-character field limit; the schema caps `findings_markdown` at 1,048,576 Unicode characters (not bytes), while the whole-document byte size is a separate limit the application enforces. Older root `findings`, `_meta.findings_markdown`, and structured `_meta.ui.findings` are migration inputs, not new tracer output fields; a structured `_meta.ui.findings` is still fully sanitized (HTML tags/control chars stripped, each string ≤ 200 chars) — only `findings_markdown` is exempt.

### Edge merging
Expand Down
4 changes: 4 additions & 0 deletions skills/etherscan-flow/schema/case.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@
"y": {
"type": "number",
"description": "Canvas y-coordinate. Tracer output orders nodes within a hop by first-flow chronology at 180-unit intervals; Etherscan Flow may persist a user-edited layout."
},
"pinned": {
"const": true,
"description": "Reserved for Etherscan Flow; the tracer never emits it. Set when a user pins the card so it keeps its x/y through dragging and Tidy Up. Absent means unpinned; the canvas deletes the field on unpin rather than writing false."
}
}
},
Expand Down
11 changes: 11 additions & 0 deletions skills/etherscan-flow/schema/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ def test_layout_is_required_and_persistable(self):
nonzero["nodes"][0]["y"] = 80
self.assertEqual([], self.schema_errors(nonzero))

def test_canvas_pin_is_accepted_only_as_true(self):
pinned = self.case()
pinned["nodes"][0]["pinned"] = True
self.assertEqual([], self.schema_errors(pinned))

for bad in (False, "true", 1):
with self.subTest(pinned=bad):
case = self.case()
case["nodes"][0]["pinned"] = bad
self.assertTrue(self.schema_errors(case))

def test_tiny_plain_decimal_string_is_accepted(self):
case = self.case()
case["edges"][0]["amount"] = "0.000000000000092695"
Expand Down
Loading