Design system
Color — two axes
Axis 1 · HUE = runtime
Axis 2 · ACCENT = indexed
Encoding — legend
Source vocabulary
The .s line on every node is source · type · address. The source token names where the indexer (or, for non-indexed nodes, where the reader) can look up the object. Only the labels below are valid in diagrams.
| Source | DipDup datasource | What's read | Visible in diagrams |
|---|---|---|---|
| TzKT | tzkt · tezos.tzkt | L1 operations, head, rollup inbox | yes — Tezos-side anchors and rollup inbox |
| EVM node | etherlink_node · evm.node + etherlink_subsquid · evm.subsquid | L2 events and transactions (node for tip, subsquid for backfill) | yes — every EVM anchor |
| Rollup node | rollup_node · http | Outbox messages and proofs | yes — Rollup outbox message |
| Tezos node | tezos_node · http | Protocol constants (one-shot at bootstrap) | no — out-of-band lookup, no on-chain object |
| Metadata | metadata · tzip_metadata | TZIP-21 token metadata | no — out-of-band lookup during ticket resolve |
| Kernel | — (not a data source) | Kernel-internal precompile or routine with no observable EVM trace | yes — pedagogical nodes (e.g. SendOutboxMessage, parse_routing) |
Kernel · … means the object lives only inside the rollup kernel — no event, no separate tx, no standard JSON-RPC anchor. Such nodes are always faded (never indexed) and exist on the diagram to keep the flow legible (e.g. show how EVM Withdrawal becomes Rollup outbox message).Layout and verification
One on-chain object = one node in the standard format (§3 · Node dictionary). Paired events (Token Mint/Burn) — as a separate node.
Mermaid captures the geometry (overflow / collisions / a “missed arrow” are impossible). Before showing — verify the render has no console errors and that line statuses/colors are in place; screenshot the diagram by uid (take_snapshot → take_screenshot).
Mermaid
flowchart TD
U(["User"]):::actor
K["<b class='t'>Kernel parse_routing</b>"]:::sys
subgraph R["L2 · EVM · ACTUAL"]
direction TB
E["<span class='glyph'>✓</span><b class='t'>Value Transfer</b><span class='s'>EVM node · transaction · 0x…feed</span><span class='ix'>✓ etherlink.on_xtz_deposit</span><span class='md'>✚ EtherlinkDepositOperation</span>"]:::evm
F["<b class='t'>Event Deposit</b><span class='s'>EVM node · event · 0xff…01</span>"]:::evm
E --> F
end
M["<span class='glyph'>◔</span><b class='t'>Event Deposit</b><span class='s'>TzKT · event · tz1Ke2h7…</span><span class='pln'>◔ planned: tezos.events</span>"]:::mich
L["<b class='t'>Value Transfer</b><span class='s'>EVM node · transaction · 0x00…00</span>"]:::evm
K -->|has code → queue| E
K -->|Michelson · tz1| M
K -->|old kernel| L
U -->|initiates deposit| K
class E indexed
class M planned
class F faded
class L legacy
class L faded
class U faded
class K faded
classDef l1 fill:#e8836b1f,stroke:#e8836b,stroke-width:1.5px;
classDef evm fill:#5aa6e01f,stroke:#5aa6e0,stroke-width:1.5px;
classDef mich fill:#6fbf731f,stroke:#6fbf73,stroke-width:1.5px;
classDef sys fill:#8b919c1f,stroke:#8b919c,stroke-width:1.5px;
classDef indexed stroke-width:2px;
classDef planned stroke-width:2px;
classDef faded stroke-width:1.5px;
classDef legacy stroke-width:1.5px;
classDef actor fill:transparent,stroke:#e8836b,stroke-width:1.5px;
linkStyle 0 stroke:#5aa6e0,stroke-width:2.4px;
linkStyle 1 stroke:#5aa6e0,stroke-width:2.4px;
linkStyle 2 stroke:#6fbf73,stroke-width:2.4px;
linkStyle 3 stroke:#5aa6e0,stroke-width:2.4px;
linkStyle 4 stroke:#e8836b,stroke-width:2.4px;
1 · Node HUE — classDef, applied inline
// fill = hue-bg (.12 alpha, 8-digit hex), stroke = hue classDef l1 fill:#e8836b1f,stroke:#e8836b,stroke-width:1.5px; classDef evm fill:#5aa6e01f,stroke:#5aa6e0,stroke-width:1.5px; classDef mich fill:#6fbf731f,stroke:#6fbf73,stroke-width:1.5px; classDef sys fill:#8b919c1f,stroke:#8b919c,stroke-width:1.5px; NODE["…"]:::evm // hue — inline on the node
color: in classDef. Mermaid turns it into the rule .class span{color … !important}, which overrides all per-line colors inside the node (handler/source turn white). Text color is set only by themeCSS (§3); classDef is the node's border/fill.2 · DECISION — one line per node
class NODE indexed // ✓ teal glyph + teal handler line class NODE planned // ◔ amber glyph class NODE faded // not indexed (opacity .45) class NODE legacy // dashed border; multiple lines per node allowed
class is a list of NODES, not classes. class NODE evm,indexed reads as “class indexed for nodes NODE and evm” — the hue won't arrive. That's why hue is inline :::evm and decision is one line per class.3 · Node dictionary — HTML label + themeCSS
NODE["<span class='glyph'>✓</span><b class='t'>Event Withdrawal</b><span class='s'>EVM node · event · 0xff…02</span><span class='ix'>✓ etherlink.on_withdraw</span><span class='md'>✚ EtherlinkWithdrawOperation</span>"]:::evm // lines: .t name · .s source (source · type · address) · .ix handler (teal) · .md model · .pln planned (amber) // themeCSS (inside mermaid.initialize) — line colors: .nodeLabel .t { display:block; white-space:nowrap; font-weight:600; color:#eef0f3; padding-right:20px } // name .nodeLabel .s { display:block; white-space:nowrap; font-size:9.5px; color:#7c828e } // source .nodeLabel .ix { display:block; font-size:9.5px; color:#3ed6c0 } // handler (teal) .nodeLabel .md { display:block; font-size:9.5px; color:#7c828e } // model .nodeLabel .pln { display:block; font-size:9.5px; color:#d9a83c } // planned (amber) .nodeLabel .glyph { position:absolute; top:-2px; right:0; font-size:17px; font-weight:700 } .node.indexed .nodeLabel .glyph { color:#3ed6c0 } // ✓ teal .node.planned .nodeLabel .glyph { color:#d9a83c } // ◔ amber .node.faded { opacity:.45 } .node.legacy rect { stroke-dasharray:5 4 }
white-space:nowrap on every line + keeping them short: the node grows wider, not taller, and doesn't break the layout. source — strictly source · type · address, nothing more. The trailing name (method, event, entrypoint, notes like input=0x / 4-arg / → receiver) lives in the title and is not repeated in source: it only widens the node. The shorter the source line, the narrower the node — and node width, multiplied across side-by-side columns, is what drives the whole diagram's width.0xff…01, tz1Ke2h7…) or a role alias (rollup, gateway, tez-ticketer). A hash earns its place when it distinguishes several similar contracts (0xff…01 XTZBridge vs 0xff…02 FABridge); an alias is clearer when there's a single well-known contract and the hash would distinguish nothing. Multi-word aliases are hyphenated.Call Contract.method, Event Name, or a role label (Transfer Ticket, Value Transfer, Rollup cement). No runtime prefix (Tezos … — the hue already encodes the runtime) and no parenthetical qualifiers ((mint) / (burn) — disambiguation belongs in the source address).only if code present, Block 3, proxy specified?, bridge.rs:401-507) don't read at node size and just clutter — if something needs explaining, extend the graphic, not the node. This isn't a ban on conditions or roadmap: recurring structural facts get a defined encoding here first (a hue, a glyph, a dedicated line class — the way roadmap is already carried by color + status glyphs), and only then appear in nodes. Until it's in this standard, it doesn't go in a node.4 · ARROW — linkStyle by declaration order
linkStyle 0 stroke:#5aa6e0,stroke-width:2.4px; // evm — solid linkStyle 4 stroke:#e8836b,stroke-width:2.4px,stroke-dasharray:6 5; // flow break — dashed (see §4.2)
opacity. Dimming is a node property (faded / legacy, §2), never an edge one.4.1 · Arrow labels — what value moves
A -->|xtz| B // xtz moves along the edge — type only, from/to obvious from nodes A -->|ticket| B // ticket moves along the edge — same rule A -->|User → Helper| B // token transfer: B is a `Call Token.transfer` node — write from → to, no `Token:` prefix A -->|mint → User| B // ERC-20 mint: B is `Call Token.mint` A -->|User → burn| B // ERC-20 burn: B is `Call Token.burn` A -->|approve Ticketer| B // approve: B is `Call Token.approve`, write target spender A -->|NAC| B // system routing labels stay as short action words
xtz or ticket — node positions already say who sends and who receives. Tokens, by contrast, always materialize as a dedicated Call Token.* node (transfer / mint / burn / approve); the arrow leading INTO that call carries from → to with no Token: prefix — already implicit from the call name. System and routing labels (NAC, SP sees commitment, with proxy → queue) stay as short action words.4.2 · ARROW — solid vs dashed
A --> B // solid — the flow continues on-chain A -.-> B // dashed — the flow leaves the chain before continuing
watchtower sees queued deposit), or a protocol delay must elapse (⏳ refutation window).5 · Actor — capsule (stadium node)
SP(["Service Provider"]):::actor class SP faded classDef actor fill:transparent,stroke:#e8836b,stroke-width:1.5px;
6 · Init — canonical block (extracted into mermaid-init.js)
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({
startOnLoad:false, securityLevel:'loose', theme:'base',
themeCSS, // the CSS string from §3
themeVariables:{
fontFamily:'JetBrains Mono, monospace', fontSize:'12px',
lineColor:'#565c67', clusterBkg:'transparent',
clusterBorder:'#1e212a', edgeLabelBackground:'#0e1014',
},
flowchart:{ htmlLabels:true, curve:'basis', nodeSpacing:42, rankSpacing:58, padding:10, useMaxWidth:false },
});
await mermaid.run({ querySelector:'.mermaid' });
Field ledger
A table lens for DB models — one row per field, live on the
data model · l2 operations page. Where flow diagrams map on-chain objects, the ledger maps a
model's columns: scope, per-runtime meaning, UI use, and writer. Shared tokens (runtime hues, ⚠/◔ glyphs, legendbox)
are reused; the table-specific mechanics below apply where a page renders a .led table.
F1 · Columns & layout
field · type · dep · wd · UI★ · EVM holds · Michelson holds · written by
colspan=2). The table is width:auto (sized to content, never stretched) and centered via a
fit-content wrapper; the legend sits in that same wrapper so it matches the table width. Both live inside the
.mapwrap, which scrolls horizontally only if the table outgrows the viewport.F2 · Badges — fixed order, half-badges for composites
// type-column badges — ALWAYS in this order: PK / FK (teal / orange) // key role — stays beside the type word null (faint) // nullable uq (green) // unique // a multi-column constraint is NOT a prose row — one half-badge per member: uq ↓ // upper member uq ↑ // lower member — both carry the SAME title = the full constraint, on hover
null → uq. Never reorder per row; the
consistency is the readability. A unique_together(a, b) becomes uq ↓ on a and
uq ↑ on b, the full constraint in a shared title tooltip — then the prose row is deleted.F3 · Scope dots
● on — field present on this side ◐ half — present, but the FK is nullable on this side ○ off — absent on this side
F4 · Cell hue = runtime · ⚠ = a real smell, not polymorphism
.evm blue text // EVM-runtime meaning / writer .mich green text // Michelson-runtime meaning / writer
written by tokens (event / op, handler / michelson_matcher)
are tinted independently, so a divergence reads at a glance.level, address, l2_account) is honest and gets no ⚠.
⚠ marks a defect: a name that lies (transaction_hash holding an op-hash), a wrong unit
(transaction_index = op.counter), or one concept named two ways across tables
(ticket_owner vs l2_ticket_owner). If everything is flagged, nothing is.F5 · ★ real consumers · ◔ proposed · detail on hover
◔ proposed writer), never in a separate "future" block. A proposed change to an existing field
is shown on that field — e.g. a gold null badge with the rationale on a title hover — not as
a duplicate row. Keep cells terse; push explanations into title tooltips. Mechanics for not-yet-shipped
planes (e.g. the matcher plane) stay in a separate experimental design page, out of this committed vocabulary.