Tutorial 1 — Your First App Chain
Open this outcome in App-Chain Studio
- Level: beginner
- Time: about 15 minutes; 25 with the optional load and effects exercises
- Outcome: three members finalize the same ordered event and expose the same state root.
This tutorial uses Yano’s self-contained Cardano devnet and the built-in
ordered-log state machine. It needs no external Cardano node, wallet, funds,
Kafka, or plugin.
1. Choose the working directory and start
Section titled “1. Choose the working directory and start”The commands in every tutorial run ./yano.sh from the directory that contains
it. Choose one setup:
From an extracted release distribution, run this in its top-level directory
(the directory containing yano.sh and yano.jar or the native executable):
cd /path/to/extracted/yano-{version}./yano.sh appchain helpFrom a source checkout, run this from the repository root:
./gradlew :app:quarkusBuild -PskipSigning=truecd app./yano.sh appchain helpThe remaining commands are identical for either setup:
export YANO_CLUSTER_DIR=/tmp/yano-tutorial-first-chain./yano.sh appchain cluster start 3The launcher starts:
- node 0 as the local Cardano L1 producer and app-chain proposer;
- nodes 1 and 2 as app-chain voting members; and
orders-chain(ordered-log),registry-chain(kv-registry), andeffects-chain(approvals) for the dependency-free effects demo.
The expected HTTP ports are 7070, 7071, and 7072. If those ports are
busy, the launcher prints the free range it selected; use that range in the
commands below.
2. Confirm agreement
Section titled “2. Confirm agreement”./yano.sh appchain cluster statusLook for:
orders-chain: AGREED (...)registry-chain: AGREED (...)AGREED means the members expose the same authenticated application root. It
does not merely mean that all three processes are running.
Open the status pages if you prefer a UI:
- http://127.0.0.1:7070/ui/app-chain/
- http://127.0.0.1:7071/ui/app-chain/
- http://127.0.0.1:7072/ui/app-chain/
3. Submit a business event
Section titled “3. Submit a business event”Submit through member 1 rather than directly through the proposer:
./yano.sh appchain cluster submit orders-chain orders \ '{"event":"order-created","orderId":"A-1001","quantity":4}' \ --node 1Member 1 authenticates and gossips the envelope. The proposer orders it, a threshold signs the block, and all members apply the same bytes.
Wait a couple of seconds, then inspect the finalized history and agreement:
curl -s http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/blocks | jq ../yano.sh appchain cluster statusThe tip advances on every member and the roots remain equal.
4. Capture a message ID and its proof
Section titled “4. Capture a message ID and its proof”For a complete proof-oriented submission, call the same public API directly:
RESPONSE=$(curl -s -X POST \ http://127.0.0.1:7072/api/v1/app-chain/chains/orders-chain/messages \ -H 'Content-Type: application/json' \ -d '{"topic":"orders","body":"{\"event\":\"packed\",\"orderId\":\"A-1001\"}"}')
echo "$RESPONSE" | jq .MESSAGE_ID=$(echo "$RESPONSE" | jq -r .messageId)sleep 3
curl -s -X POST \ "http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/proof-subjects/finalized-message-v1/proof" \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg id "$MESSAGE_ID" ' {coordinates:{"message-id":$id}, view:"latest", claim:{claimId:"recorded",operands:{}}, includeEvidence:false}')" \ | jq '{stateRoot:.proof.stateRoot,presence:.proof.presence,position:.fact.fields,claim:.claimResult.satisfied}'The typed subject resolves the public message ID to its namespaced physical state key and connects that record to the member’s committed state root. Tutorial 7 connects that root to a Cardano anchor.
5. Run a small ordered-log load test
Section titled “5. Run a small ordered-log load test”The distribution includes a parallel load driver for the running local cluster. Start with a bounded workload of 500 messages, 10 concurrent submitters, and payloads of approximately 256 bytes:
./yano.sh appchain cluster loadtest orders-chain -n 500 -c 10 -s 256Plain load-test mode is intended for any-bytes machines such as
ordered-log. It submits numbered UTF-8 bodies on the load topic, waits
for the pending pool to drain, and reports two different rates:
==================== throughput ==================== submitted attempts : 500 accepted (2xx) : 500 dropped (429 pool): 0 errors : 0
SUBMIT rate : ... msg/s finalized msgs : 500 in ... block(s) FINALIZE rate : ... msg/s end-to-end rate : ... msg/s====================================================- SUBMIT rate measures how quickly an ingress REST API accepts messages.
- FINALIZE rate measures how quickly messages enter threshold-certified blocks; this is the meaningful chain-throughput measurement.
- dropped (429 pool) means backpressure worked because the pending pool filled. Reduce concurrency or tune the pool and block limits deliberately.
- errors are non-backpressure request failures and should be investigated.
Spread submissions across all ready members to exercise gossip from every ingress path:
./yano.sh appchain cluster loadtest orders-chain -n 1000 -c 20 -s 256 --spreadThen confirm that every member still exposes the same committed root:
./yano.sh appchain cluster statusThis is a functional throughput exercise, not a production benchmark. Results
include the local machine, JVM/native runtime, devnet producer, configured
block interval, payload size, and member count. Record those inputs when
comparing runs. The test appends real finalized messages and therefore advances
the retained orders-chain history.
For capacity settings and workload boundaries, see the
ordered-log reference.
6. Preserve and restart
Section titled “6. Preserve and restart”stop keeps both L1 and app-chain data:
./yano.sh appchain cluster stop./yano.sh appchain cluster start 3./yano.sh appchain cluster statusThe retained tips and roots should return unchanged before new traffic is finalized. This is a useful distinction:
- restart: same chain identity and retained state;
- clean: delete the chain and create a new identity/history.
7. Try effects and governed member onboarding
Section titled “7. Try effects and governed member onboarding”Effect demo
Section titled “Effect demo”The default effects-chain can demonstrate the full emit, external-worker,
result, and proof lifecycle without Kafka, S3, IPFS, or a real webhook:
./yano.sh appchain cluster effect demo./yano.sh appchain cluster effect demo "order A-1001 approved"With no argument, the demo uses hello from Yano effects; one quoted argument
replaces that message. The command prints the captured payload, confirmed
delivery, and proof availability.
Briefly, the command:
- creates a unique one-approval item on the separate
effects-chain; - wraps your text in a JSON payload, then submits
PROPOSEandAPPROVEcommands to itsapprovalsstate machine; - keeps the item decision
APPROVEDand emits one generic app-finaldemo.webhookeffect when the approval threshold is reached; - acts as a simulated external worker that claims the effect and reports a synthetic successful delivery—no real webhook is called; and
- feeds the result back through the effect lifecycle and checks that the finalized effect proof is available.
The supplied text is illustrative data. It is not automatically linked to an
event on orders-chain, even if it contains an order id. A production workflow
should carry an explicit business id or finalized source message id in its
committed command/effect payload. The proof establishes that the effect intent
was committed; a real external action additionally depends on a trusted
executor and verifiable receipt.
For the complete lifecycle and production webhook configuration, continue with webhook effects.
Governed member onboarding
Section titled “Governed member onboarding”You can also govern, start, catch up, and verify a fourth node on the same host:
./yano.sh appchain cluster node join 3./yano.sh appchain cluster statusFor an externally managed node, the lower-level
appchain cluster member add <public-key> command records membership but does
not configure or start the external process. Same-host node join performs
both steps.
When finished:
./yano.sh appchain cluster cleanunset YANO_CLUSTER_DIRWhat you just proved
Section titled “What you just proved”- A submission can enter through any member.
- A threshold, not one REST server, finalizes the ordered block.
- All members deterministically derive the same state root.
- A retained restart recovers the same application history.
- A message can have an MPF inclusion proof against that root.
You did not yet prove Cardano settlement or the truth of the order fields. Those are separate trust layers.
Go deeper
Section titled “Go deeper”- Change
--threshold 3and observe that all three votes are now required. - Read the consensus guide for proposer, vote, certificate, replay, and catch-up mechanics.
- Continue with registry ownership and state proofs.