Skip to content

feat(examples): two-way cross-chain messaging demo (L1<->L2 + saya)#580

Draft
kariy wants to merge 23 commits into
mainfrom
demo/cross-chain-messaging
Draft

feat(examples): two-way cross-chain messaging demo (L1<->L2 + saya)#580
kariy wants to merge 23 commits into
mainfrom
demo/cross-chain-messaging

Conversation

@kariy
Copy link
Copy Markdown
Member

@kariy kariy commented May 29, 2026

Cross-Chain Dice — an app built as a Katana appchain

A runnable demo (under examples/cross-chain-game/) showing how to build an
application on a Katana appchain with two-way L1↔L2 messaging, settled by
saya and indexed by Torii. It doubles as a worked example for a build
guide.

Architecture

  • Settlement Katana ("L1") hosts the piltover core + a Dojo score world.
  • Appchain Katana ("L2") — a rollup (--tee mock, --messaging.enabled) —
    runs a Dojo game world and settles to piltover via a saya-tee sidecar.
  • Two Torii indexers (one per chain) expose model/event state; the React
    client reads everything over Torii SQL and writes via starknet.js.

Flow (one round trip)

  1. Buy (L1→L2): piltover.send_message_to_appchain → relayed into the game
    world's mint_game #[l1_handler] (adds a credit).
  2. Play (L2): play_game rolls a score on-chain and send_message_to_l1.
  3. Settle: saya proves the appchain block and update_states piltover.
  4. Bank (L2→L1): the score world's claim_score consumes the settled message.

What's here

  • Contracts — two Dojo worlds (cairo/game, cairo/score): models, systems,
    events, the l1_handler, and send_message_to_l1/consume_message_from_appchain.
  • Deploy — two-pass sozo migrate (scripts/) wiring the worlds across chains.
  • Orchestrationup.sh/down.sh bring up 2 Katanas + saya + 2 Torii + UI.
  • Client — React + Vite + shadcn/ui Arcade→Vault game reading via Torii.
  • Guideexamples/cross-chain-game/docs/: architecture, why each service
    exists, the contracts, deployment, and how the client queries state.
  • saya-patch/ — required saya-tee fix (Poseidon vs keccak L1→L2 hash) + rationale.

Notes

  • Local-only demo: throwaway dev keys, --dev.no-fee, --mock-prove (exercises
    the messaging/settlement plumbing, not proof soundness).
  • Toolchain pinned in .tool-versions (sozo/torii/scarb); Dojo consumed by path
    from a sibling checkout.
  • app/src/deployments.json is a placeholder, regenerated each up.sh run.

🤖 Generated with Claude Code

kariy and others added 2 commits May 29, 2026 03:27
Add `examples/cross-chain-game`, a runnable demo of Katana L1->L2
messaging. A purchase on a settlement Katana ("L1") calls
`send_message_to_appchain`; the appchain Katana ("L2") relays it via
`--messaging.enabled` and runs the `mint_game` L1 handler. A React +
Vite + shadcn/ui frontend reacts to the appchain state and deep-links
every tx hash to each node's `--explorer`.

Includes the appchain Cairo contract, starknet.js deploy scripts, and
`up.sh`/`down.sh` orchestration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the cross-chain game store with the reverse direction. The demo now
runs the unified rollup + saya settlement stack: a settlement Katana hosting
the piltover core, an appchain rollup (--tee mock) that settles to it, and a
saya-tee --mock-prove sidecar.

- L1->L2 (buy a game): piltover.send_message_to_appchain -> mint_game handler.
- L2->L1 (sync a score): achievements.send_message_to_l1 -> saya settles the
  block -> score_registry consumes it via consume_message_from_appchain.

Adds the achievements (appchain) and score_registry (settlement) contracts,
a single deploy.ts, saya-stack orchestration in up.sh/down.sh, and a two-flow
shadcn UI with a live saya settlement indicator and per-direction tx links.

Requires a one-function saya-tee v0.4.0 fix (see saya-patch/): saya hashed
L1->L2 messages with the Ethereum keccak formula, but a Starknet-settled
appchain needs the Poseidon formula Katana commits to, or L1->L2-consuming
blocks fail piltover's 'tee: invalid messages' check and stall settlement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kariy kariy changed the title feat(examples): cross-chain L1->L2 messaging demo feat(examples): two-way cross-chain messaging demo (L1<->L2 + saya) May 29, 2026
kariy and others added 21 commits May 29, 2026 19:08
Reframe the demo as "Cross-Chain Dice" with explicit phases: buy games
(L1->L2), play a game (roll on the appchain), and auto-publish the score
(L2->L1, settled by saya). Merge the appchain game_minter + achievements
contracts into one `game` contract (mint_game l1_handler, play_game that
rolls on chain and emits send_message_to_l1, view fns); score_registry
unchanged. Update deploy.ts/lib.ts + deployments shape accordingly.

Frontend:
- Light, white-based theme with violet/green accents (shadcn/ui).
- Event-sourced feeds rebuilt from chain events (MessageSent/GameMinted,
  GamePlayed/ScoreClaimed) so they survive refresh, plus a reconciler that
  resumes publishing any played-but-unclaimed game.
- Clickable message cards open a modal with the full flow + tx hashes
  (shadcn Dialog); compact single-row cards with tip-to-tip flow arrows.
- 3-at-a-time scrollable card containers; Framer Motion enter animations.
- Tooltips (shadcn) for the saya settled/tip indicator; code identifiers
  rendered as inline <code>; all addresses link to their explorer page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Show an intro dialog on load explaining the demo (appchain + cross-chain
  messaging), with an About button in the header to reopen it.
- Add a top-right info icon on each phase card that opens a modal detailing
  the services involved and the exact contracts/functions called for that
  step (buy/mint, play, publish), with an "evaluated as…" note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Separate playing from publishing. Rolling now only runs play_game on the
appchain (rolls + emits the L2->L1 message); it no longer auto-publishes.
Each played score gets an explicit "Settle to L1" button that calls
claim_score / consume_message_from_appchain on the settlement layer.

The button is gated on saya: it shows "Awaiting saya…" until saya has
settled the score's block, then "Settle to L1". chain.ts now tracks each
play's appchain block (for the gate) and matches claims to plays by score
so out-of-order settling still lines up with the right L1 tx.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- All shadcn buttons get cursor-pointer (added to the Button base).
- Rename phase 3 to "End a game".
- Game (purchase) cards: drop the L1→L2 subtitle, make them fully rounded
  pills, and show a single status icon (spinner → check) instead of the
  stepper/"Minted" text, right-aligned with no chevron.
- End-a-game (score) cards: drop the L2→L1 subtitle; keep the stepper +
  Settle action.
- Smaller card padding and font sizes; retune each feed's max-height so it
  still shows exactly 3 of the shorter cards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite the demo UI around a game model: play free on the appchain
(Arcade / L2) and bank scores to the settlement layer (Vault / L1). Feeds
are event-sourced so they survive refresh, banking is an explicit user
step, and per-step info modals plus an intro dialog explain the flow.

Pin the layout to the viewport (h-screen with a min-height floor) so the
Arcade and Vault lists scroll internally instead of overflowing the page,
and move the "Under the hood" plumbing panel into a modal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the "Under the hood" button with a circular settings icon that
rotates on hover, and turn the Credits/Best HUD chips into pills aligned
to the same height with their label and value on one row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite the two contracts as Dojo worlds and read game state through Torii
instead of contract views.

- cairo/: two Dojo packages — the appchain `game` world (Stats/GameConfig
  models, `mint_game` l1_handler, `play_game` system, GameMinted/GamePlayed
  events) and the settlement `score` world (Leaderboard/PlayerScore models,
  `claim_score` consuming the settled message, ScoreClaimed event). Events are
  keyed by a unique sequence so Torii keeps a row per mint/play/bank.
- scripts/: deploy via two-pass `sozo migrate` (score world first, then the
  game world wired with the score system address); profiles generated per-run
  from deployments.json, addresses parsed from the manifests.
- up.sh/down.sh: migrate both worlds with sozo and run a Torii indexer per
  chain (settlement :8081, appchain :8082, distinct relay ports); prereq checks
  + banner updated.
- app/: chain.ts reads models + event feeds over Torii SQL (writes stay on
  starknet.js); the L1 purchase log still comes from the settlement RPC. The
  "Under the hood" modal now lists each chain's Torii endpoint and Dojo world.
- Pin the toolchain in .tool-versions (sozo 1.8.7, torii 1.8.16, scarb 2.13.1);
  dojo is consumed by path from the sibling checkout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a six-part guide under examples/cross-chain-game/docs/ that generalizes
how to build an application as a Katana appchain, using the cross-chain-game
demo as the worked example throughout:

- README: big picture + one action traced end-to-end across every component
- architecture: the Dojo world model and the two-chain (L2 play / L1 anchor) split
- services: why each service exists and how it works (Katana ×2, piltover, saya, Torii ×2)
- contracts: Dojo models/systems/events, both messaging directions, cross-world wiring, gotchas
- deployment: toolchain, sozo migrate, two-pass wiring, annotated up.sh bring-up
- client: querying via Torii SQL, the cross-Torii join, the write path

Each chapter pairs the general pattern with a real file:line snippet from the
demo. The example README now links to the guide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The intro modal now offers "Start tutorial" / "I already know". The tutorial
is a spotlight tour over the real UI that explains each operation in appchain
terms (chains, contracts, messages) rather than how to play:

- 7 steps anchored to live elements (Arcade, Insert coin, Roll, rolls-to-bank,
  saya gauge, Vault, settings) via data-tour attributes; popover placement
  adapts (below/above with viewport clamping, or left of a full-height card).
- Insert coin and Roll are interactive: the user clicks the real button and the
  tour auto-advances once the on-chain effect lands (credit minted / roll
  recorded). The overlay is click-through so the highlighted control is
  genuinely clickable; non-action steps keep a click-blocker + Next/Done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ial actions

Add a "Bank the roll to L1" step that highlights the real Bank button and
auto-advances once a run settles (claim_score -> consume_message_from_appchain);
the Vault step now describes the resulting L1 record. Interactive steps (Insert
coin, Roll, Bank) drop the Skip button so the user must perform the action to
proceed; the header X still exits the tour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add GitHub permalinks so devs can cross-reference the real source while going
through the app:

- New app/src/source.ts maps each contract symbol shown in the UI (play_game,
  mint_game, claim_score, consume_message_from_appchain, the models/events, …)
  to its file + line, and the Code component auto-links those symbols in the
  "how it works" modals, the tutorial, and "Under the hood".
- The docs guide's `path:line` references (37 across architecture/services/
  contracts/deployment/client) become clickable permalinks.

All links are pinned to a commit SHA (true permalinks: exact lines, survive the
branch merge). Bump SOURCE_REF + the doc links if the referenced files move.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In a real game the purchase originates from the game's own L1 contract, not a
direct client call to the messaging core. Add a settlement-side `store` Dojo
world whose `buy_game` runs the store's rules (tally a sale; a hook for charging
a token / checking supply) and then calls `send_message_to_appchain` on the
piltover core. Deployed as a third world (after game, init = [piltover, game
system]); the frontend's purchaseGame now calls `store.buy_game`. As a result
`mint_game`'s L2 `from_address` is the store contract — provenance L2 can trust.

Also in this change:
- The credits count next to "Insert coin" opens a modal listing the L1→L2
  messages (purchases) with L1-send / L2-mint tx links.
- Referenced piltover symbols (send_message_to_appchain, MessageSent) and the
  new buy_game are linked to their source; the info/tutorial/under-the-hood copy
  reflects the store hop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the store world

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ght + install)

up.sh now preflights before bringing the stack up: it auto-installs the Dojo
toolchain (sozo/torii/scarb via `asdf install`, idempotent) alongside the JS
deps, and fails fast with the exact command for the heavy prerequisites it won't
build for you — the katana binary, the patched saya, and the sibling dojo
checkout the cairo packages depend on. So `./up.sh` is the single command to
stand up the whole demo (services + migrations + UI). README updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…orial

Make clear the appchain settles to L1 with TEE proving, run in mock mode for
local dev. Add a shielded callout to the intro modal and a one-line reminder to
the saya tutorial step (left-aligned, not justified).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A header button (workflow icon, next to the gear) opens a "Contract flow"
modal: a human-friendly diagram of the two chains and four contracts (store,
piltover core, score on L1; game on L2) with the two message directions between
them (L1→L2 send/relay/mint, L2→L1 send/saya-settle/consume). Function names are
clickable source permalinks; a footer notes saya's bridging role, Torii
indexing, and mock proving.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s L2)

Add a checklist to architecture.md for choosing which operations/state belong on
the appchain (L2) vs the settlement layer (L1) — keep-on-L2 / put-on-L1
heuristics, tie-breakers, and the demo mapping. Update the world tables to
include the store world (three worlds) for consistency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reflight

Final review pass: update prose and code references that predated the L1 `store`
world and the up.sh preflight.

- README + guide: "two worlds" → three (store + score on L1, game on L2); buy is
  store.buy_game (not a direct piltover call); add store to the What's-where
  table, the topology, the migration order, and the bring-up sequence (now with
  a preflight step).
- Re-point every file:line permalink at the current code (line numbers drifted
  in chain.ts / App.tsx / up.sh / lib.ts after the store + preflight changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t nits

Point every doc/source permalink at the commit that contains the store world +
preflight (so the line numbers match what's shown). Also fix the remaining
client→piltover buy framing in services/guide and a broken section anchor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… source.ts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant