bridge indexing · design system
Design system
Color — two axes
Axis 1 · HUE = runtime
L1 / bridge--l1
EVM L2--evm
Michelson L2--mich
alias / NAC--alias (reserved)
system / kernel--sys
Axis 2 · ACCENT = indexed
indexed ✓--idx
planned ◔--planned
Encoding — legend
Legend
Hue
L1 / bridge
L2 EVM
L2 Michelson
system / kernel
Decision
✓ indexed
◔ planned
not indexed
Status
legacy · remains in mainnet
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 ≠ data source.
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 object = one node
One on-chain object = one node in the standard format (§3 · Node dictionary). Paired events (Token Mint/Burn) — as a separate node.
self-check before showing
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,stroke-dasharray:6 5,opacity:0.55;
linkStyle 4 stroke:#e8836b,stroke-width:2.4px,stroke-dasharray:6 5,opacity:0.55;
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
⚠️ Pitfall: do not write
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
⚠️ Pitfall: a comma in
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 }
Node lines do not wrap.
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.Address — hash or alias, no strict rule. Both read fine: a truncated hash (
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.Title stays short. Forms:
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).No stray "why / when" lines. A node says what is indexed. Free-text notes about conditions or phase (
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 linkStyle 3 stroke:#5aa6e0,stroke-width:2.4px,stroke-dasharray:6 5,opacity:.55; // legacy
Mermaid trade-off: the condition label on the arrow stays neutral gray (per-label color matching the runtime hue isn't available out of the box). A deliberate concession for the mermaid medium.
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 / ticket — type only, no from/to. When xtz or a ticket moves along the edge, just write
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.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' });