Skip to content

Repository files navigation

Message Machine Terminal

A vintage-CRT chat terminal for the Message Machine — an on-chain messaging contract by Takens Theorem for The Vessel.

Holders of machine-type Vessel crafts tune their craft to the Message Machine and broadcast text. The message is stored on Ethereum and rendered as pixel- projected letters on the vessel's artwork (via Takens' on-chain LetterProjection contract). This terminal reads the entire channel and lets holders transmit.

⧉ MESSAGE MACHINE TERMINAL
[06-06 17:54] hot.takens.eth ▸ #365 :: hey dav
[06-06 17:55] producedbydav.eth ▸ #8243 :: hey.takens
[06-08 18:05] stephensantoro.eth ▸ #10000 :: A fully on-chain font framework
─────────────────────────────────────────────
wallet hot.takens.eth   SIGNAL: OPEN ▸ #365
hot.takens.eth #365:$ ▌

Quickstart

npm install
npm run dev        # http://localhost:5173
npm test           # unit tests (vitest)
npm run test:e2e   # e2e with mock wallet (playwright)
npm run build      # → dist/index.html  (single self-contained file)

The build output is one HTML file. Drop it on any static host, an IPFS gateway, or open it from disk — no server, no env vars, no build step for integrators. See docs/INTEGRATION.md.

How it works

Concern Source Fallback
Chat history Blockscout API (tx calldata, decoded client-side) live getMessage polls (no timestamps)
Token ownership Vessel indexer GraphQL on-chain balanceOf cross-check → Blockscout NFT inventory → on-chain enrichment
Channel roster indexer GraphQL (machine filter) history-derived candidates, current binding verified on-chain
Live messages / simulations getMessage + projector via the wallet's own node when an extension exists, public RPC pool otherwise
ENS names viem reverse resolution, 24h localStorage cache shortened address
Writes user's browser wallet (EIP-6963 / window.ethereum), every tx eth_call-simulated first

The cascade follows the multi-source pattern of dapp-query by 1001-digital, implemented by hand because this app needs empty-vs-error semantics: an indexer once re-synced from a months-old block and confidently answered every query with empty results — so here, an empty ownership answer is only believed if the chain itself agrees (balanceOf == 0). Total source failure surfaces as an error, never as "NO VESSEL DETECTED".

Why calldata instead of events? The MessageMachine emits no events — setMessage writes a mapping silently. The transaction list is the history, so we decode 0x648345c8 inputs from Blockscout. Details in docs/CONTRACT.md.

Terminal commands

Command Effect
/connect link a browser wallet (Rabby preferred, any EIP-6963/injected works). No signature is requested — connecting only reveals your address; ownership is proven when you sign a transmission. Auto-switches the wallet to Ethereum mainnet.
/disconnect unlink the wallet (revokes the site permission where supported) and return to read-only
/bind <id> tune one of your machine-type crafts to the Message Machine (one tx, reversible)
/use <id> choose which bound craft transmits
/style <preset | key=value…> set how your text projects on the artwork (one tx). Named presets — stock inverted ghost palette large — compose with field overrides, e.g. /style large fontsize=16 bg=20; raw 8 numbers also accepted. The terminal echoes the exact numbers before you sign.
/preview <message> free simulation: renders the exact pixel artwork your message would produce, in-terminal, with a fit verdict — no transaction
/sound toggle sound effects
/help list commands
anything else broadcast it as a message from your active craft (one tx, fit-checked first)

Typing / opens an autocomplete palette (Discord/Claude style): arrow keys to navigate, Tab to fill, Enter to run. Completion is unix-style down to the argument level: /bind 2 filters your actual bindable craft ids, /use completes your bound crafts, /style offers presets and key=value fields each annotated with what the knob does. Values ending in = fill and keep completing; Enter on a complete value fills and submits.

Layout (IRC/BBS): one chat stream + an ABOARD rail showing every craft bound to the machine, grouped by owner (ENS/address once, their crafts and each craft's live message beneath) and sorted by most recent activity. The rail renders glow-free for legibility and scrolls independently. ▤ aboard toggles it; on mobile it becomes a slide-over drawer (chat goes full-width) dismissed via the in the rail header or a tap outside it.

Terminal niceties: text in the log is selectable/copyable (clicks never steal an active selection), typing anywhere refocuses the prompt, the log sticks to the newest line unless you've scrolled up to read history (any submit snaps back), every confirmation wait shows an ASCII spinner that resolves to ✓/✗, craft numbers everywhere link to the official viewer, and your last-used craft is remembered across reloads.

Read path: if a wallet extension is installed, ALL reads go through the wallet's own node from page load (eth_call needs no authorization) with public RPCs as fallback — wallet-less visitors use public RPCs only. The terminal never silently reads a non-mainnet chain.

Access states

  1. CHANNEL OPEN — you own a machine-type craft bound to the Message Machine. Type to transmit.
  2. Machine craft, unbound — your machine craft points elsewhere (e.g. the Entropy router). /bind <id> retunes it.
  3. Vault/capsule only — only machine-type crafts can run a Machine; you can read but not transmit. Links to claiming a machine-type craft.
  4. NO VESSEL DETECTEDclaim a vessel to join.

Why every message needs a wallet signature

setMessage is gated on-chain by ownerOf(tokenId) == msg.sender. Browser wallets always require per-transaction confirmation for externally-owned accounts — a web page cannot "auto-approve" them, and a delegated burner key can't sign either because the contract checks the owner, not a delegate. The terminal compensates by simulating every transaction (eth_call) before prompting — you are never asked to sign something that would revert — and by rendering messages optimistically while they confirm. A message costs gas (~50k gas ≈ a few cents at typical basefees).

Design language

The palette is derived from the Vessel renderer's on-chain color modes:

Mode Color Share of collection Used here for
0 greyscale rgb(v,v,v) ~98.4% base phosphor (white/grey on black) — including hyperlinks, which render as quiet grey underlines, not blue text
1 pure red rgb(255,0,0) ~0.41% errors, SIGNAL: READ-ONLY
2 pure green rgb(0,255,0) ~1.03% your prompt/cursor, your messages, SIGNAL: OPEN, the active palette row
3 pure blue rgb(0,0,255) ~0.15% call-to-action links only, as inverse video (white text on a blue block) — saturated blue text on black proved illegible and is never used

Accents appear as rarely on screen as the colors do on-chain. If your craft has a rare color mode, your prompt tints to that exact channel. CRT treatment (scanlines, flicker, glow, glitch) and all sound is WebAudio-synthesized in-browser — zero audio assets. The soundscape is an old mainframe room: typewriter keystrokes, per-character teletype clacks as incoming transmissions print, a vacuum-column tape drive (clunk-shhh-clunk) when new data lands on a poll, relay snaps on state changes (connect/disconnect/craft switch), a carriage-return bell on confirmed sends, and a boot sequence. Polls are silent when nothing changed.

Artwork fit simulation (the projector gate)

The LetterProjection is a pure contract function, so the terminal computes the exact artwork a message produces before you sign anything (free eth_calls). On every send it checks whether the full text renders on your craft's canvas; if not, it shows a pixel preview of the truncated artwork in the terminal, the precise character budget, and offers: fit (auto-set fontsize then send — two transactions; only when an oversized custom font is the cause), force (send anyway — the full text is ALWAYS stored on-chain; only the artwork truncates), or cancel. /preview <msg> runs the same simulation on demand, no transaction.

Empirical projector facts (probed on mainnet — the contract is unverified): payload length == tokenId (capacity is the canvas), fontSize acts as glyph scale, fontSize=0 auto-shrinks to minimum scale — so small crafts have a hard character budget no style can extend (a ~700-byte craft is only a 27×27 grid ≈ a few dozen chars; a 9000-series craft holds ~14× more). Current craft style is reconstructed from updateTokenOpts transaction history, since the contract has no getter.

Repository map

src/config.ts      addresses, ABIs, style presets/fields, query-param overrides
src/codec.ts       pure calldata encode/decode + #token-ref splitting (golden-vector tested)
src/history.ts     Blockscout history scan + updateTokenOpts reconstruction
src/data.ts        validated read cascade: indexer → balanceOf cross-check →
                   Blockscout inventory → on-chain enrichment (an EMPTY answer
                   is only believed if the chain agrees)
src/style.ts       /style parsing: presets + key=value → 8-number optsArray
src/projector.ts   LetterProjection simulation: fit analysis, char budget,
                   truncation probe, payload→pixels for previews
src/ens.ts         ENS reverse resolution + cache
src/wallet.ts      EIP-6963 connect, wallet-first read clients, chain guard
src/vessel.ts      access classification + bind/send/style (simulate-first)
src/ui/            CRT renderer (palette, roster, spinner, preview canvas),
                   stylesheet, WebAudio sound synth
tests/unit         52 vitest tests incl. on-chain golden vectors and the
                   indexer-wipe incident regressions
tests/e2e          11 playwright scenarios w/ mock wallet + route mocks
                   (+ opt-in LIVE=1 smoke against real infrastructure)
docs/              CONTRACT.md · AGENTS.md · INTEGRATION.md

Credits

  • Message Machine & LetterProjection — Takens Theorem
  • The Vesselworldcomputer.art
  • dapp-query1001-digital (the multi-source read pattern ours is modeled on)
  • CRT aesthetic technique adapted from a private terminal project; recolored to the Vessel palette

License

CC0-1.0 — public domain. Take it, fork it, embed it.

About

Vintage-CRT chat terminal for the Message Machine (by Takens Theorem) on The Vessel — fully on-chain messages, no backend

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages