Small, runnable examples of what you can build on Yano X app chains — application-specific replicated ledgers that settle on Cardano.
Each example is a real, if small, use case: a few parties who need a shared record that none of them controls. Each one runs locally, needs no funds and no external Cardano node, and comes with a demo script you can follow or present.
Examples are written in different languages on purpose — Java, JavaScript — to show that an app chain is ordinary infrastructure you talk to over HTTP.
| # | Example | Language | What it shows |
|---|---|---|---|
| 01 | Shared access-change audit log | Java | A log two companies share and neither controls, with per-entry proofs |
| 02 | Consortium service registry | JavaScript | Only the owner can change an entry — and an app verifies answers for itself, with no SDK |
| 03 | Pharmaceutical batch release | Java | Named people sign, not nodes — multi-stage QC/QA/QP approval, role replacement, Cardano anchoring |
| 04 | Catalyst milestone disbursement | Java | The approved hash is the Cardano transaction id — approve off-chain, then pay exactly what was approved |
| 07 | Webhook effects | JavaScript | A finalized approval calls an ERP — and the outcome goes back into consensus |
| 09 | Anchor and verify | Java | A verifier that reads Cardano and nothing else — no Yano node is asked anything |
| 20 | Anchor Canvas (game) | HTML/JS | A shared pixel wall: CIP-30 wallet sessions, a deterministic fold over an ordered log, pixels proven against the Cardano anchor — static, GitHub Pages-ready |
| 21 | Anchor Games (game center) | HTML/JS, Java plugin | One password account and eight browser games on one ordered-log chain whose topic registry protects games/: every move is signed by the player's own one-hour session key, delegated by the publisher's service, and the members refuse anything else. Anchor Galaxy, Anchor Arcade, Land Rush, Comet Derby, Reef, Deep Sonar, Neon Rush and Sealed Duels, also as a plugin edition whose rules run on the members. |
The full catalog of planned examples, grouped from foundations through Cardano anchoring to custom plugins, is in USE_CASES.md.
You need:
- Java 25 —
java -version - Maven — for the Java examples
- Node.js 20+ — for the JavaScript examples
bash,curl,jq— standard on macOS and Linux- An extracted Yano X JVM distribution, 0.1.0-pre6 or later, in this
directory (
yano-x-jvm-0.1.0-pre6/), downloaded from the releases page
If the distribution lives somewhere else, point at it:
export YANO_X_DIST=/path/to/yano-x-jvm-0.1.0-pre6Every example works the same way:
cd 01-audit-log-java
./cluster start 3 # start its chain — three members on a local devnet
./demo.sh # the walkthrough
./cluster clean # stop and wipe when you're done./cluster also takes status, stop [n], logs <n> [-f] and clean.
./cluster start sets the example's application up the way real operators
would, with the release's own setup commands: yano.sh appchain new from the
example's template, then request, seal, join and start for each of three
members on a private Cardano devnet that the lead member produces. See
00-shared for the steps.
Anchoring is on by default. Every example anchors its state root to the
local Cardano devnet in script mode — a Plutus V3 thread NFT per chain, with a
validator-enforced datum chain, started with yano.sh appchain anchor. There
is nothing to set up. Opt out with ./cluster start 3 --no-anchor.
Each example gets its own chain, its own data directory and its own ports, so you can leave one running while you try another. Nothing is written back to the extracted distribution.
README.md this file
USE_CASES.md the full catalog of examples and what each one proves
pom.xml aggregator — lets an IDE see every Java example at once
00-shared/ tooling every example reuses
01-audit-log-java/ one folder per example
02-service-registry-js/
03-batch-release-java/
04-catalyst-disbursement-java/
07-webhook-effects-js/
09-anchor-verify-java/
20-anchor-canvas-game/
21-anchor-game-center/
yano-x-jvm-*/ the extracted distribution (not committed)
Inside an example:
README.md what it is and how it works
DEMO.md the demo script — read this first
cluster start / stop / status
demo.sh guided walkthrough
chain/<app>.template.yaml the application: its chains, recipes and settings
src/ or index.js the application
Open the top-level folder. The root pom.xml is an aggregator, so IntelliJ
IDEA imports every Java example as a module in one go, and picks up JavaScript
examples from their package.json automatically.
The aggregator is not a parent — no example inherits from it, and no example declares it. Each one keeps a complete build file and can be copied out of this repository and built on its own:
mvn package # build every Java example, from the root
mvn -pl 01-audit-log-java package # build just one
cd 01-audit-log-java && mvn package # or build it standaloneWhen you add a Java example, add one <module> line to the root pom.xml.
JavaScript examples need nothing.
If IDEA still shows an example as plain folders, the import was cached from
before the aggregator existed: right-click the root pom.xml → Add as Maven
Project, or use Maven tool window → Reload All Maven Projects.
A small ledger shared by a fixed group of parties. Each party runs a node. A record counts only after a threshold of them has signed the block containing it, and every party keeps the full history.
That gives you four things a normal database cannot:
- A shared order nobody can rewrite afterwards
- A single state root — one hash covering everything recorded, so disagreement is immediately visible
- Per-record proofs you can hand to an outsider, without giving them the rest of the log
- Cardano anchoring — publish that root on L1 periodically, so the timestamp is verifiable by anyone, for pennies
And it stays honest about what it is not: membership is a configured list of keys, not an open validator set. These examples say so where it matters.
Every sealed chain carries three state fields that look alike but behave
completely differently. You no longer write them: yano.sh appchain seal puts
them into <app>.chain, and refuses a template that tries to set them.
state.commitment-profile: mpf-blake2b256-v1
state.format-fingerprint: 91ee14091200f1e24659112d640e877e9177779dcc81dd06117f013e9190082b
state.genesis-id: 4e63218fc2d753d6a9b32073484942d88d54dff21837bc24ddcd0efd5484c899| Field | Same on every member? | Across different chains | Where it comes from |
|---|---|---|---|
commitment-profile |
must be identical | same, if they use the same profile | the chain's recipe |
format-fingerprint |
must be identical | same, if they use the same profile | derived — a constant of the profile |
genesis-id |
must be identical | must differ | derived by seal from the draft's invite, the chain id and its settings |
To see them for an example, after ./cluster start:
jq '.genesis.chains[] | {id, state: (.settings | with_entries(select(.key|startswith("state."))))}' \
.yano/app/*.chainThis is a closed catalog — StateCommitmentProfiles in yano-core-api defines
exactly three, and only two can be run today. The fingerprint is
blake2b256("yano-state-commitment-format-v1\0" || canonical profile descriptor),
so it is fixed per profile:
commitment-profile |
format-fingerprint |
Verification |
|---|---|---|
mpf-blake2b256-v1 |
91ee14091200f1e24659112d640e877e9177779dcc81dd06117f013e9190082b |
off-chain and on-chain |
jmt-blake2b256-v1 |
10c06baa78234478a3ea94d2c61e23cea8b145c9416ece35688da9be8106e97a |
off-chain only |
jmt-poseidon-bls12381-v1 |
879153d220352224204b1e289af2ad234489f52870857a5ed99e500dad4d49b2 |
none yet — runtime gated |
Every recipe these examples use seals
mpf-blake2b256-v1, and it is the only profile the examples here run on. The two JMT rows are correct values for profiles nothing in this repo has booted.
The table above is transcribed from the library, so it can go stale. If you are on the JVM, ask the library directly — the catalog is public API, and this needs no running node:
<dependency>
<groupId>org.yanoproject</groupId>
<artifactId>yano-core-api</artifactId>
<version>0.1.0-pre19</version>
</dependency>Already depending on org.yanoproject.x:yano-x-client? You have it — the client
pulls yano-core-api in at compile scope.
import org.yanoproject.api.appchain.state.StateCommitmentProfile;
import org.yanoproject.api.appchain.state.StateCommitmentProfiles;
import org.yanoproject.x.client.ProofVerifier;
import java.util.HexFormat;
for (StateCommitmentProfile profile : StateCommitmentProfiles.all()) {
boolean verifiable = ProofVerifier.profileMetadata(profile.id())
.map(ProofVerifier.ProfileMetadata::verifierAvailable)
.orElse(false);
System.out.printf("%-26s %s verifier=%s%n",
profile.id(),
HexFormat.of().formatHex(profile.formatFingerprint()),
verifiable ? "available" : "gated");
}mpf-blake2b256-v1 91ee14091200f1e24659112d640e877e9177779dcc81dd06117f013e9190082b verifier=available
jmt-blake2b256-v1 10c06baa78234478a3ea94d2c61e23cea8b145c9416ece35688da9be8106e97a verifier=available
jmt-poseidon-bls12381-v1 879153d220352224204b1e289af2ad234489f52870857a5ed99e500dad4d49b2 verifier=gated
The rest of that surface:
| Call | Gives you |
|---|---|
StateCommitmentProfiles.all() |
every profile in the closed catalog |
StateCommitmentProfiles.find(id) |
Optional — for validating a config value |
StateCommitmentProfiles.require(id) |
same, but throws on an unknown id |
StateCommitmentProfiles.MPF_BLAKE2B256_V1 |
the id constants, instead of string literals |
profile.formatFingerprint() |
the 32 bytes to put in format-fingerprint |
profile.nativeVersioning() / .physicalDelete() |
how deletes and versions behave |
ProofVerifier.profileMetadata(id) |
verifierAvailable — whether this release can verify its proofs |
yano-core-api is also where StateCommitmentIdentity lives, which is what
validates the three fields together at startup — worth reading if you want the
exact rule rather than this summary.
A non-JVM client can get the same values for one chain from
GET /api/v1/app-chain/chains/{chainId}/state/identity:
{ "profile": "mpf-blake2b256-v1",
"formatFingerprint": "91ee1409…",
"genesisId": "…", "stateRoot": "…" }Two caveats: it reports the chain's own profile, not the catalog, and the route is not in the shipped REST reference — it works, but it is undocumented.
Choosing between them:
mpf-blake2b256-v1is the default and what every example here uses. It is the only profile that may setl1-proof-consumption-required— the others fail with "L1 state-proof consumption is supported only by MPF". If a Cardano script must ever verify your proofs, this is the only option.jmt-blake2b256-v1is an off-chain-only comparison backend. It uses native versioning and tombstones where MPF uses physical delete, so a removed key behaves differently.jmt-poseidon-bls12381-v1exists so its consensus identity is fixed now. The shipped verifier reports it as unavailable; you cannot run on it.
Nobody chooses the fingerprint. The runtime recomputes it from the named profile and refuses to start if the sealed value disagrees — a tripwire that catches a configuration naming one profile while expecting another format.
32 bytes unique to each chain. Not a key, not a secret — it is what stops two
chains with otherwise identical settings from being mistaken for each other,
and what binds a proof to your chain. It is also what a Cardano anchor datum
carries as chainGenesisId.
seal derives it from the draft's invite (random per draft), the chain id and
the chain's resolved settings, so every member gets the same value from the
sealed file, and a fresh ./cluster clean && ./cluster start 3 gives a fresh
chain. It cannot be changed later without sealing a new application.
GET /state/identity reports a different genesis id than the sealed one.
That is expected: the runtime derives the effective value from it plus the
committed application profile.
A node won't start. Read its log — ./cluster logs 0 — and look for the
first ERROR. Do not clean to fix a startup problem; you will lose the
evidence.
Ports already in use. Each example picks its own range. Override it on the
first start (the ports go into the sealed member cards, so afterwards it takes
a ./cluster clean):
EXAMPLE_HTTP_BASE=7300 EXAMPLE_SERVER_BASE=13600 ./cluster start 3Changing an example's template after it was sealed. The sealed file wins
until you start over: ./cluster clean && ./cluster start 3.
"Roots differ" in tips. A fresh cluster needs a second or two for every
member to catch up. Run it again.
MIT, matching Yano and Yano X.