Task: Build “The Anatomy of SATP” — a static interactive scrollytelling explainer

Task: Build “The Anatomy of SATP” — a static interactive scrollytelling explainer

1. Project

Build a static, interactive, scrollytelling explainer titled “The Anatomy of SATP” that opens up the Secure Asset Transfer Protocol as implemented in Hyperledger Cacti, step by step:

client app -> gateway -> proposal -> commencement -> lock -> commit -> finalisation

Primary audience: software developers who know roughly what a blockchain is and want a deep, visual, step-by-step understanding of how two gateways move an asset across two ledgers that share no consensus.

The site must feel like a polished interactive science explainer — not a dashboard, not a blog post, not a slide deck, not API documentation.

Reference for tone, structure and interaction design: https://www.royvanrijn.com/anatomy-of-an-llm/ (source: https://github.com/royvanrijn/anatomy-of-an-llm — read its AGENTS.md, docs/visual-style.md and docs/chapter-writing-style.md for the design philosophy, but do not copy its stack; see the constraints below.)

2. Hard constraints

  • Final output must deploy as static files. It must build with this repository’s existing Jekyll setup without adding plugins, gems, or other dependencies.
  • No backend. No runtime API calls. No live gateway calls. No npm, no bundler, no framework, no CDN. Vanilla ES2019 JavaScript, plain CSS, inline SVG only.
  • All protocol data lives in static, hand-authored, deterministic JSON under assets/data/. All example messages are hardcoded dummy data and must be visibly labelled as illustrative, exactly as the reference project labels toy data.
  • The page must render meaningfully with JavaScript disabled (progressive enhancement) and must work when opened over file:// (inline JSON fallback if fetch fails). JavaScript is allowed and expected for progressive enhancement, but the static HTML remains the baseline experience.
  • The demo must use the same recognisable site-level header and footer as the other pages. Because index.html remains a standalone file, mirror the shared navigation, recent-post links and credits with static relative links, then keep them in sync with _includes/navbar.html and _includes/footer.html.
  • Do not modify anything outside satp-demo/. The initial scaffold and this prompt are the only exceptions made before implementation begins.

3. Repository context

  • Repo: rafaelapb/rafaelapb.github.io, an existing Jekyll site with _config.yml, shared layouts, and root-level static pages. The SATP implementation being explained remains Hyperledger Cacti’s packages/cactus-plugin-satp-hermes in the separate hyperledger-cacti/cacti repository.
  • Build the tutorial as a self-contained, dependency-free page under satp-demo/, served at /satp-demo/. Use a plain index.html with no front matter so Jekyll copies it unchanged and the same file opens cleanly over file://. Use relative asset URLs.
  • Do not change _config.yml, shared layouts, global assets, or generated _site/ files. The demo owns all of its HTML, CSS, JavaScript, JSON, and SVG.
  • Format new HTML, CSS, and JavaScript with 2-space indentation and double quotes in JavaScript. This repository has no documented conventional-commit, DCO, ESLint, or cspell requirement; keep commits focused and use feat: add interactive SATP demo for the final change unless the maintainer requests another title.

4. Ground truth — read these before writing a line

Source of truth for stages, steps and ordering is the upstream hyperledger-cacti/cacti repository. Read these files from GitHub main before implementation. Do not invent steps or rename tags.

File What it gives you
packages/cactus-plugin-satp-hermes/src/main/typescript/core/satp-protocol-map.ts Canonical SATP_PROTOCOL_MAP: stage → ordered steps → tag, description, role, sequence, MessageType
.../src/main/proto/cacti/satp/v02/common/message.proto CommonSatp, TransferClaims, NetworkCapabilities, Asset, NetworkId, and all enums
.../src/main/proto/cacti/satp/v02/service/stage_0.protostage_3.proto Per-stage request/response messages and gRPC services
.../core/stage-services/client/stage1-client-service.ts How Stage 1 client messages are built, signed, hashed, logged
.../core/stage-services/server/stage1-server-service.ts How Stage 1 messages are validated and answered
.../core/stage-handlers/stage1-handler.ts Handler orchestration and adapter hook points
.../core/session-utils.ts saveMessageInSessionData, SessionData.satpMessages.stageN
.../docs/api3-adapter-spec.md, package README.md Adapter/webhook hook points per stage+step

External references:

  • IETF spec: https://ietf-satp.github.io/draft-ietf-satp-core/draft-ietf-satp-core.txt (see the @url at the top of satp-protocol-map.ts).
  • Flow diagram to model the Chapter 2 layout on: https://github.com/ietf-satp/draft-ietf-satp-core/blob/main/figures/message%20flow%20diagram/gateway-message-flow-asset-transfer-v20PNG.png

4.1 Verified protocol facts (re-verify against the files, then use)

Stage 0 — Transfer Initiation and Negotiation (explicitly experimental; not in draft v2; subject to change) newSessionRequest C·18 → checkNewSessionRequest S → newSessionResponse S·19 → checkNewSessionResponse C → preSATPTransferRequest C·20 → checkPreSATPTransferRequest S → preSATPTransferResponse S·21 → checkPreSATPTransferResponse C

Stage 1 — Transfer Initiation and Commencement Flows transferProposalRequest C·INIT_PROPOSAL=6 → checkTransferProposalRequestMessage S → transferProposalResponse S·INIT_RECEIPT=7 / INIT_REJECT=8 → checkTransferProposalResponse C → transferCommenceRequest C·9 → checkTransferCommenceRequestMessage S → transferCommenceResponse S·10 (ACK-Commence) → checkTransferCommenceResponse C (implemented in Stage2ClientService)

Stage 2 — Asset Locking and Escrow lockAsset C (ledger operation, no wire message)lockAssertionRequest C·11 → checkLockAssertionRequest S → lockAssertionResponse S·12 → checkLockAssertionResponse C (implemented in Stage3ClientService)

Stage 3 — Commitment and Finalization commitPreparation C·13 → checkCommitPreparationRequest S → mintAsset S (ledger op, destination)commitReadyResponse S·14 → checkCommitPreparationResponse C → burnAsset C (ledger op, source)commitFinalAssertion C·15 → checkCommitFinalAssertionRequest S → assignAsset S (ledger op, destination)commitFinalAcknowledgementReceiptResponse S·16 → checkCommitFinalAssertionResponse C → transferComplete C·17 → checkTransferCompleteRequest S → transferCompleteResponse S·22 → checkTransferCompleteResponse C

Stage 1 wire formats (stage_1.proto; service SatpStage1Service with RPCs TransferProposal, TransferCommence):

TransferProposalRequest  { common, transfer_init_claims, transfer_init_claims_format,
                           network_capabilities, multiple_claims_allowed,
                           multiple_cancels_allowed, client_signature }
TransferProposalResponse { common, hash_transfer_init_claims, transfer_counter_claims,
                           timestamp, server_signature }
TransferCommenceRequest  { common, hash_transfer_init_claims, client_transfer_number,
                           client_signature }
TransferCommenceResponse { common, server_transfer_number, server_signature }

CommonSatp: version, message_type, session_id, transfer_context_id, sequence_number, resource_url, action_response, credential_block, payload_profile, payload, payload_hash, client_gateway_pubkey, server_gateway_pubkey, hash_previous_message, error, error_code

TransferClaims: digital_asset_id, asset_profile_id, verified_originator_entity_id, verified_beneficiary_entity_id, originator_pubkey, beneficiary_pubkey, sender_gateway_network_id, recipient_gateway_network_id, client_gateway_pubkey, server_gateway_pubkey, sender_gateway_owner_id, receiver_gateway_owner_id, max_retries, max_timeout, amount_from_originator, amount_to_beneficiary, process_policies[], merge_policies[]

NetworkCapabilities: sender_gateway_network_id, signature_algorithm, supported_signature_algorithms[], lock_type, lock_expiration_time, permissions, developer_urn, credential_profile, application_profile, logging_profile, access_control_profile, subsequent_calls, history[]

Enums to render symbolically and numerically: MessageType (0–22), LockType, SignatureAlgorithm, CredentialProfile, ClaimFormat, Error (34 codes), ERCTokenStandard, TokenType.

Per-message lifecycle inside a gateway — client side: build message → sign(Signer, safeStableStringify(msg))saveSignaturesaveHashsaveTimestamp(PROCESSED)dbLogger.persistLogEntry({sessionId, type, operation, data, sequenceNumber}) → send. Server side: adapter hook beforecheck*Message() (including checkNetworkCapabilities, checkTransferClaims, DLT support check against bridgeManagerClient.getAvailableEndPoints()) → saveMessageInSessionData → build and sign response → adapter hook after. Rejection sets session state REJECTED or CONDITIONAL_REJECTED.

5. Visual style

The demo’s overall visual language must be recognisably similar to The Anatomy of an LLM: match its editorial pacing, typographic hierarchy, paper-like surfaces, generous whitespace and sticky prose-with-figure rhythm. Adapt that language to SATP and the Cacti palette; do not copy its implementation or visual assets.

Adopt the reference project’s “Light Organic Editorial” direction, re-tinted for Cacti. All values as CSS custom properties in one :root block.

Character — light paper-like background (not pure white), soft card edges, subtle texture gradients, serif-forward display headings with clean sans body, restrained accent usage for meaning not decoration, generous vertical rhythm.

Avoid — neon/cyberpunk visual language, dashboard-heavy UI, glowing effects, over-styled gradients, gradient text on headings.

Palette — page #FBFAF5, deep section tint #F3F0E6, base surface #FFFDF7, active surface #F4EFE2, primary text #171717, secondary #3F3F46, muted #71717A, border rgba(39,39,42,0.14). Also ship a “Night Paper” dark variant (warm dark ink #17191D with parchment-tinted cards, muted highlights, no neon) under prefers-color-scheme: dark plus a manual toggle.

Semantic accents — one colour per concept, used consistently everywhere: Stage 0 negotiation, Stage 1 agreement, Stage 2 locking, Stage 3 commitment; plus client-gateway vs server-gateway role, and source-network vs destination-network. Accents mark active traces, selected states and concept mappings only — never large background fills.

Typography — display: "Fraunces", "Iowan Old Style", Georgia, serif; body/UI: "IBM Plex Sans", "Avenir Next", "Segoe UI", sans-serif; eyebrow text in uppercase body font with letter spacing. Body line length 55–75 characters. Use only system/webfont-free fallbacks — no font CDN.

Layout — sections are narrative beats, not app panels. Each chapter section targets ≥1 viewport height. Sticky visual region beside scrolling prose. Navigation jumps to real section anchors. Motion explains state change and is never decorative filler; transitions subtle and short; prefers-reduced-motion: reduce disables all non-essential motion while preserving comprehension.

6. Shared primitives — build these first, reuse everywhere

Do not build each chapter as a one-off widget. Implement a small vanilla-JS component library in assets/js/primitives/, each a factory returning { mount, update, reset, destroy }:

Primitive Purpose
ChapterShell Section wrapper: eyebrow, title, subtitle, prose column, figure slot
StickyVisual Sticky figure that stays in view while adjacent prose scrolls
StepperControls Play / pause / prev / next / restart / speed, keyboard + ARIA
LaneDiagram The SVG actor-lane canvas: actors, lanes, arrows, ledger bands
PacketGlyph An animated message travelling along an arrow
LedgerBand A chain band with block glyphs and an asset-state badge
ActorNode Client app / gateway / server app node with icon, label, role tint
StepList Ordered, clickable, highlight-current list of protocol steps
MessageCard Collapsible, annotated, syntax-highlighted JSON message view
FieldAnnotation Hover/focus tooltip: field name, proto type, plain-language meaning
SourceChip Inline monospace link to an exact file/line on GitHub main
ToyDataBanner Persistent “these values are illustrative” label

Shared design tokens: consistent border, radius, shadow, text scale and spacing ramps. Visuals must teach, not decorate.

7. Chapter writing rules

Every chapter opens in this exact order, before any control or piece of jargon appears:

  1. Concept in plain language (1–2 short paragraphs).
  2. Why this step exists in the SATP pipeline.
  3. Why not the alternatives? — answer the obvious “why not just use a bridge / a trusted custodian / an HTLC?” question.
  4. Then controls, message field names, enum names, code references.

Language rules: no implementation detail in the first line (never open with INIT_PROPOSAL or CommonSatp); subtitles concept-first and human-readable; short sentences; concrete examples. Bad: “How TransferProposalRequest carries TransferClaims and NetworkCapabilities.” Better: “How two gateways agree on the terms of a transfer before anything moves.” Then later: “This agreement travels in a message called TransferProposalRequest.”

Before Chapter 1, include a site introduction (not a chapter, not in chapter nav) that explains what the page is for, how to read and navigate it, and sets the expectation: from an application asking for a transfer to an asset existing on another chain.

8. File layout

satp-demo/
  index.html                       # standalone semantic HTML, prose, mount points
  README.md                        # how to preview, where data comes from, sync rules
  ATTRIBUTION.md                   # every icon, its source project and licence
  scratch.html                     # primitive harness, removed or retained as dev aid
  assets/css/satp-tutorial.css     # tokens + all styling
  assets/js/satp-tutorial.js       # bootstrap, nav, theme, scroll observer
  assets/js/primitives/*.js        # shared component library (section 6)
  assets/js/chapter-02-stages.js   # Chapter 2 controller
  assets/js/chapter-03-messages.js # Chapter 3 controller
  assets/data/satp-stages.json     # mirror of SATP_PROTOCOL_MAP
  assets/data/stage1-messages.json # hardcoded Stage 1 example messages
  assets/data/field-annotations.json
  assets/img/icons.svg             # local sprite of <symbol> icons
  plans/progress.md                # milestone tracker

assets/data/satp-stages.json is a hand-maintained transcription of SATP_PROTOCOL_MAP. For each step record tag, description, role, sequence, messageType (numeric) and messageTypeName (symbolic), plus a derived kind:

  • "validation" — tag starts with check
  • "ledger" — no messageType (lockAsset, mintAsset, burnAsset, assignAsset)
  • "message" — everything else

Because standard JSON does not allow comments, put a top-level _meta object in that file stating that it mirrors core/satp-protocol-map.ts, naming the upstream URL, and requiring both to be updated together. State the same rule in README.md.

9. Iconography

Use Tabler Icons (MIT — https://github.com/tabler/tabler-icons/blob/main/LICENSE), copied as raw SVG paths into a single local sprite assets/img/icons.svg as <symbol id="icon-...">, referenced with <use href="assets/img/icons.svg#icon-gateway">. No CDN, no runtime third-party fetch. Fall back to Lucide (MIT) for any missing glyph.

Concept Tabler icon
Gateway gateway / router
Blockchain / ledger box, cube, boxes (chained)
Asset / token coin, wallet
Client application app-window, browser
Message / packet mail, package, mail-forward
Lock / mint / burn / assign lock, circle-plus, flame, arrow-right-circle
Signature / hash / key signature, hash, key
Accept / reject check, x

Record every icon used, with source set and licence, in ATTRIBUTION.md. Do not assume Cacti or Hyperledger marks exist locally; any copied upstream mark must be stored under satp-demo/assets/img/ and have its exact source and licence recorded.

10. Chapter 1 — What SATP is and why it exists

6–9 substantive paragraphs following the opening pattern of section 7, covering:

  1. The problem. Independent ledgers share no consensus and cannot natively agree. Custodial bridges concentrate trust and have been the ecosystem’s largest source of value loss. Asset transfer needs atomicity across two systems that agree on nothing.
  2. Why not the obvious alternatives — a trusted custodian, a wrapped-token bridge, a pure HTLC swap — and what each fails to give you.
  3. The gateway model. SATP puts no logic on-chain; it standardises the conversation between two gateways, each the sole authorised agent for its network. One takes the client role (source), the other the server role (destination). Applications talk to their own gateway; gateways talk to each other.
  4. Sessions. Every transfer is a SATPSession: session id, transfer_context_id, monotonic sequence_number, separate client- and server-side session data, messages filed under SessionData.satpMessages.stageN.
  5. The four stages as a narrative arc — negotiate (0), agree (1), lock (2), commit and finalise (3). Stress the ordering invariant: value is created on the destination before it is destroyed on the source. Mint, then burn, then assign.
  6. Asset semantics. lockAsset, mintAsset, burnAsset, assignAsset as the four ledger primitives; the Asset / NetworkId / TokenType / ERCTokenStandard model.
  7. Integrity and recoverability. Canonical serialisation (safeStableStringify), signature, hash, timestamp, durable log entry — all before the message is sent. hash_previous_message chains messages so neither party can rewrite history; the log is what makes crash recovery possible.
  8. Extensibility. The adapter layer hooks before/during/after/rollback at any stage+step — e.g. pausing at checkTransferProposalRequestMessage for a compliance decision.
  9. A caveat on Stage 0 — a Cacti implementation extension, not standardised in draft v2, explicitly subject to change.

Every technical claim carries an inline SourceChip linking to the exact file and, where stable, line range on GitHub main.

Close the chapter with a reference table of all stages and steps, built at load time from satp-stages.json: Stage · Seq · Step tag · Role · Kind · MessageType · Description, with a text filter and role/kind toggles. It must render as a plain HTML table without JS.

11. Chapter 2 — Anatomy of the stages (interactive)

A full-bleed StickyVisual containing a LaneDiagram, driven by satp-stages.json.

Actors, top to bottom:

  1. Client Application — icon app-window
  2. Gateway 1 — client role — icon gateway
  3. Gateway 2 — server role — icon gateway
  4. Server Application — icon app-window

Plus two LedgerBands: Network A (source) and Network B (destination), each with chained block glyphs and an asset badge whose state label updates through the transfer — A: available → locked → burned; B: — → minted → assigned.

The client application initiating the transfer into Gateway 1 and the server application being settled by Gateway 2 must both be visible. This is a four-party picture, not a two-gateway picture. Model the geometry on the IETF flow diagram.

Behaviour

  • Stage tabs 0 | 1 | 2 | 3 plus an “All stages” mode.
  • kind: "message" steps animate a PacketGlyph along the arrow, in protocol order, tinted by stage and direction.
  • kind: "validation" steps pulse inside the receiving gateway node with a check badge — making explicit that validation happens at the receiver, not on the wire.
  • kind: "ledger" steps descend from the acting gateway into its LedgerBand with the matching icon and update the asset badge. Stage 2 shows lockAsset on Network A. Stage 3 shows mintAsset (B), then burnAsset (A), then assignAsset (B), in order.
  • StepperControls: play/pause, step back/forward, restart, speed 0.5× / 1× / 2×.
  • StepList beside the diagram highlights the current step and is clickable to jump.
  • Hover or focus on any step opens a detail card: tag, role, sequence, kind, MessageType name + numeric value, the one-line description from the protocol map, and a SourceChip to the implementing service file.
  • A persistent caption states in one sentence what the selected stage achieves — e.g. Stage 2: “the source asset is immobilised and the receiver is given cryptographic evidence of that fact.”
  • Optional scroll-linked advance: as the reader scrolls past each stage’s prose, the sticky diagram advances to that stage. Manual controls always override. Disabled under reduced motion.
  • Keyboard: arrows step, space toggles play, Tab reaches every step; aria-live region announces the current step.
  • Default state before interaction is meaningful: the full static diagram with every arrow labelled, rendered even without JS.

12. Chapter 3 — Anatomy of the Stage 1 messages (interactive)

A message explorer covering Stage 1 only — four wire messages: TransferProposalRequest (INIT_PROPOSAL=6), TransferProposalResponse (INIT_RECEIPT=7, with the INIT_REJECT=8 variant behind a toggle), TransferCommenceRequest (=9), TransferCommenceResponse (ACK-Commence, =10).

Behaviour

  • A compact two-gateway mini LaneDiagram at the top, the four exchanges as clickable arrows, with the interleaved check* steps shown as receiver-side badges.
  • Clicking an arrow opens a MessageCard: the hardcoded example message as pretty-printed, syntax-highlighted JSON. Field names match the proto exactly (snake_case), including nested common (CommonSatp), transfer_init_claims / transfer_counter_claims (TransferClaims) and network_capabilities (NetworkCapabilities). Nested objects collapse. Enums render as LOCK_TYPE_HASHLOCKTIME (4) — symbolic name plus numeric value.
  • FieldAnnotation on hover/focus for every field of the four messages and the three nested types: proto type plus a plain-language explanation of what it is for and who sets it. Author these in field-annotations.json. This is the pedagogical core of the chapter — do not stub it.
  • Hash-chain toggle: highlights hash_previous_message in each message and draws connectors back to the message it commits to, plus payload_hash and hash_transfer_init_claims, showing how Stage 1 becomes tamper-evident.
  • Signature toggle: highlights client_signature / server_signature and shows the signing pipeline (safeStableStringifysignsaveSignaturesaveHashsaveTimestamppersistLogEntry) with SourceChips to each function.
  • Reject-path toggle on the proposal response: what changes when the server sets error, error_code (ERROR_...) and session state REJECTED / CONDITIONAL_REJECTED, pointing at checkTransferProposalRequestMessage.
  • Every MessageCard footer carries three SourceChips: the proto definition, the client-side builder function, the server-side check* validator.

Dummy data — one coherent fictional scenario reused across the whole page: 100 units of an ERC-20 token moving from a Hyperledger Besu network (“Network A”) to a Hyperledger Fabric network (“Network B”). A UUID-shaped session id, plausible hex pubkeys and signatures shown truncated with an ellipsis, sequence_number incrementing correctly across the four messages, and hash_previous_message values that are internally consistent between messages. A ToyDataBanner must be visible stating these values are illustrative and were not produced by a real gateway.

Where Cacti’s proto has an applicable field, seed the scenario with values from the IETF draft’s examples in Sections 8.1–8.7: session id d66a567c-11f2-4729-a0e9-17ce1faf47c1, transfer context id 89e04e71-bba2-4363-933c-262f42ec07a0, digital asset id 2c949e3c-5edb-4a2c-9ef4-20de64b9960d, asset profile id 38561, Alice and Bob distinguished names, the published example public keys, ES256, HASH_TIME_LOCK, a 120-second lock, TLS_AES_128_GCM_SHA256, timestamp 2024-10-03T12:02+00Z, and the published Stage 1 hashes. Adapt names and nesting to the exact Cacti v02 proto fields; the draft’s camelCase JSON examples are explanatory spec objects, not Cacti wire dumps. Preserve the draft values where semantics align, add the 100-unit Besu-to-Fabric context only in Cacti fields that support it, and never invent a mapping merely to retain a spec field. Document each adaptation in stage1-messages.json under _meta.specAdaptations.

13. Milestones

Work one milestone at a time. Create and maintain satp-demo/plans/progress.md. After each milestone, update the plan and report changed files, verification performed, and open questions. Keep diffs focused. Stop after the requested milestone.

  1. Shell, style tokens, navigation, site introduction.
  2. Shared primitives (section 6) with a scratch harness page.
  3. satp-stages.json transcription + Chapter 1 prose and reference table.
  4. Chapter 2 lane diagram, static rendering first, then animation and controls.
  5. Chapter 2 ledger bands, ledger operations, stage captions, scroll linkage.
  6. Chapter 3 mini diagram, message cards, field annotations.
  7. Chapter 3 hash-chain, signature and reject-path toggles; polish; a11y; docs.

14. Definition of done — per chapter

  • Static build works; page renders correctly with JS disabled.
  • Dev preview run and manually verified in a browser; interactions exercised.
  • No runtime errors in the browser console; no network requests to third parties.
  • Meaningful default view before any interaction.
  • At least one meaningful interaction.
  • Shared primitives reused; no one-off widgets.
  • Illustrative values labelled as illustrative.
  • Protocol step tags, roles and MessageType numbers match satp-protocol-map.ts exactly.
  • Mobile layout works down to 380px (lane diagram becomes a vertical timeline).
  • Reduced motion respected and comprehension preserved.
  • A reset control exists for each interactive widget.
  • Explanation is concise; the chapter opens with concept and “why” before implementation.
  • A first-time reader can answer “what is this?” after the first screen and “why do we need this?” before reaching any control.
  • WCAG AA: contrast, visible focus rings, full keyboard operation, meaningful ARIA labels.
  • HTML, CSS, JavaScript, JSON, and SVG syntax checks pass using locally available tools; do not add a formatter or linter dependency solely for this page.

A task is not complete if the build passes but the UI is broken at runtime. Report: pages checked, interactions performed, console errors found (or none).

15. Out of scope — do not add unless asked

Live gateway connections, real transactions, wallet integration, user accounts, backend, database, analytics, authentication, crash-recovery deep dive, Stage 2/3 message-level detail (Chapter 3 is Stage 1 only), Weaver, or any other Cacti plugin.

16. Deliverable

One pull request titled feat: add interactive SATP demo, with a description walking through each chapter, listing the shared primitives, stating that all displayed data is illustrative, and noting the sync requirement between assets/data/satp-stages.json and core/satp-protocol-map.ts.