A browser app to create, browse, respond to, and tally on-chain surveys and polls on Cardano, implementing CIP-179 (transaction metadata label 17).
Surveys live entirely in transaction metadata — no smart contracts, no custom backend required to read them. Responses can be public or sealed (timelock-encrypted with drand for delayed reveal), eligibility is scoped by on-chain role (DRep, SPO, CC, Stakeholder, Keyholder), and tallies are computed client-side directly from chain data.
Status: active development, end to end. The app covers the whole survey lifecycle, and the Tier-1 serving backend validates responses, snapshots stake/voting-power weights at each survey's end epoch, and finalizes closed surveys into content-addressed result artifacts that anyone can re-derive from chain data with
packages/verifier. Seebackend/ARCHITECTURE.mdfor the design andbackend/TALLY-SPEC.mdfor the counting rules.
A survey can be advertised by a Conway governance Info Action (CIP-179 Action → Survey linkage): the action's anchor metadata carries the survey's ref, and Tessera surfaces the link on the explore and survey pages once the action's voting deadline matches the survey's end epoch.
The app ships a small helper page at /propose-info-action to build, sign,
and submit that Info Action from a CIP-30 wallet: load a CIP-108 anchor
document, and the page validates its shape, extracts the linked survey, and
checks the epoch alignment before letting you submit. You can pin the exact
anchor bytes to your configured IPFS providers (or host them yourself) so the
served document matches the on-chain hash.
| Path | What it is |
|---|---|
frontend/app |
The browser app — SolidJS + Vite + TypeScript. |
packages/cip179 |
The reusable cip-179 package: label-17 codec plus the cross-implementation domain / tally / txproof / tlock surface. |
packages/core |
Tessera app core (cardano-tessera-core): the DataSource seam, Explore list/health payloads, keyset paging, the survey-list aggregation adapter, and config. The reusable domain/tally surface lives in cip-179. |
packages/koios |
The Koios read path (KoiosDataSource, tally inputs), shared by direct mode, backend, and verifier. |
packages/respond-core |
cardano-tessera-respond-core: the pure, framework-free answering core (drafting, responder eligibility, i18n factory, lazy sealed wrapper) shared by the app and the widget. |
packages/respond-ui |
cardano-tessera-respond-ui: the shared SolidJS per-question body components, so the app and the widget cannot drift in answering behavior. |
packages/respond-widget |
cardano-tessera-respond: the embeddable <tessera-respond> custom element — answer a survey anywhere, emitting a ready-to-attach label-17 payload. Framework-agnostic; wallets/chain stay host-side. |
packages/respond-react |
cardano-tessera-respond-react: React 18/19 bindings for the widget — typed props synced as DOM properties, tessera:* events as callbacks. |
packages/verifier |
Standalone CLI that re-derives a survey's result artifact from chain data and checks its content hash. |
examples/ |
Minimal React and Svelte host apps for the widget, built in CI so framework compatibility regressions fail a build. |
backend/server |
Tier-1 serving backend: cached chain reads, response validation, weight snapshots, artifact finalization. Node or CF+D1. |
backend/deps |
Indexer submodules (Adder / Yaci Store / Oura) for a future Tier-2; design notes in backend/*.md. |
Requires Node ≥ 22.5 and pnpm ≥ 10 (install pnpm). This is a pnpm workspace — install once at the repository root:
pnpm install
pnpm --filter cardano-tessera-backend dev # terminal 1
pnpm --filter tessera-app dev # terminal 2The app serves at http://127.0.0.1:3000, reading chain data through the local
backend — no Koios token needed, for reads or for building transactions
(they are signed and submitted by your CIP-30 wallet). The dev server points
at http://localhost:8787 by default; set TESSERA_BACKEND_URL to use
another address.
Both halves default to Preview. To run the same pair against preprod, pin
the network on each side — the app checks the backend's /health and refuses
one serving a different network:
pnpm --filter cardano-tessera-backend dev:preprod # terminal 1
pnpm --filter tessera-app dev:preprod # terminal 2Each backend network caches into its own tessera-cache-$NETWORK.sqlite, so
switching costs a re-scan but never mixes two chains' records.
Alternatively, skip the backend and let the browser scan Koios
directly (the power-user/offline path): a dev server does this with an empty
TESSERA_BACKEND_URL= pnpm --filter tessera-app dev, a deployed build when
its backend URL is configured empty.
That path requires an authenticated Koios token (tier 1 is free): the anonymous
tier does not send CORS headers, so browser requests need one. Paste it in the
app's Settings; it is stored in the browser and never built into the bundle.
One deployment serves one network, on both halves and with no runtime
switch: the app is built for a network and refuses a backend whose /health
reports a different one. Frontend build configuration is public — it ships in
the bundle — but deployment-specific, so it comes from build-time environment
variables (the git-ignored frontend/app/.env.deploy) rather than committed
code; the backend's Cloudflare bindings likewise live in a git-ignored
wrangler.toml. Both have a committed .example to copy. The variables and
their rules are in each half's README.
IPFS reads race a built-in list of public gateways (no config); IPFS pinning (for authoring external content / rationales) uses per-provider API tokens entered in Settings, stored only in the browser.
The repo is a pnpm workspace over the paths above, plus the examples/ host
apps. Packages are consumed from TypeScript source (Vite aliases / exports
pointing at src), so cross-package edits are live with no build step.
From the repository root:
| Command | What it does |
|---|---|
pnpm -r type-check |
Type-check every package. |
pnpm -r test |
Run every package's unit tests (Vitest). |
pnpm --filter cardano-tessera-backend dev |
Run the Tier-1 backend locally (see its README). |
pnpm --filter cardano-tessera-backend dev:preprod |
Same, pinned to preprod (dev:preview / dev:mainnet likewise). |
pnpm --filter tessera-app dev |
Start the app's Vite dev server. |
pnpm --filter tessera-app dev:preprod |
Start the app's dev server in preprod mode. |
pnpm --filter tessera-app build |
Production build of the app (the preview target). |
pnpm --filter cardano-tessera-verifier verify -- --backend <url> --survey <tx>:<i> |
Re-verify a survey's final result artifact from chain data. |
Formatting is Prettier over the whole repo: pnpm format / pnpm format:check
at the root.
Both halves deploy to Cloudflare with wrangler, one deployment per network:
the backend as a Worker + D1 + Cron
(pnpm --filter cardano-tessera-backend deploy:preview / deploy:preprod /
deploy:mainnet, after the one-time wrangler.toml and D1 setup in
backend/server/OPERATIONS.md), the app as static Workers assets
(pnpm --filter tessera-app deploy:preview / deploy:preprod /
deploy:mainnet — each bakes the values from frontend/app/.env.deploy and
uploads dist/; see frontend/app/wrangler.toml). The backend/deps submodules
are not needed for any of this; to fetch them anyway:
git submodule update --init --recursive.
Contributions are welcome. Until a CONTRIBUTING.md lands, the basics:
- Open an issue to discuss substantial changes before investing in a PR.
- Keep the build green — CI runs these on every PR, in this order:
pnpm -r test,pnpm test:operator-scripts,pnpm -r type-check,pnpm format:check. Tests come before type-check because the widget's test script builds the rolled-upd.tsits typed tests need. CI then runspnpm -r build, which needs afrontend/app/.env.deployto exist. - Match the existing code style — Prettier is the source of truth for formatting.
The code in this repository is licensed under the Apache License 2.0.