Skip to content

Cardano anchoring

Anchoring periodically commits the app ledger’s position — height, block hash, and state root — onto Cardano. The node builds, signs, and submits the transaction through its own mempool and tx diffusion, and confirms it through its own L1 sync. No external API or provider is involved.

You’ll learn: the two anchor modes, how a script-anchor advance is checked by members and by Cardano, how to fund and configure the anchor wallet, and what an anchor does and does not prove.

yano.app-chain.anchor.mode selects one:

metadata (default)script
Anchor transactionA plain tx with the anchor payload in tx metadataA Plutus V3 thread-NFT UTxO; a validator enforces the datum chain on-chain
What L1 enforcesNothing — a data-only commitmentIncreasing height, unchanged ledger identity, one continuing thread output, no value drain, and m-of-n member signatures on every advance
SigningAnchor wallet onlyWallet plus threshold co-signed member witnesses
SetupFund the walletFund, then one-time admin/anchor/bootstrap per chain
FeesDepend on the transaction size and current protocol parametersAlso include script execution costs
MaturityStablePreview

Both modes work on the devnet and on public networks. Script anchors are proven on preprod with a real Plutus V3 mint and validator-enforced co-signed advances.

Choose metadata when you want cheap public timestamping of a root that verifiers will check against the chain’s own certificates. Choose script when L1 itself should refuse an advance that lacks the member threshold.

The anchor leader checks on every tick. It anchors the current tip when there is at least one new block and either:

  • anchor.every-blocks blocks have accumulated since the last anchor (default 10); or
  • anchor.max-interval-minutes have passed since the last attempt (default 60).

After the leader starts, its first anchor fires as soon as there is anything to anchor. Blocks are produced only when there are messages, so a quiet ledger anchors nothing new.

Anchor a state rootIllustrative data

What you're seeing: a three-member ledger with threshold 2, anchoring in script mode every 2 blocks. Step through one advance, then try metadata mode, a forged root, or an offline leader.

  1. Trigger The anchor leader is the node with anchor.enabled, node 0 on a local cluster. On its tick, if new blocks exist and every-blocks have accumulated since the last anchor, or max-interval-minutes have passed, it starts an advance. App ledger finality never waits for this.
  2. Build The leader builds a transaction that spends the thread UTxO and writes the next inline datum: chain and genesis identity, height 42, its block hash and state root, member keys, and threshold. It lists the members as required signers, adds its own witness, and sends the request on ~anchor/sign.
  3. Check Each member checks the request against its own ledger and its own Cardano view. The leader gets no trust: a member signs only a root it computed itself.
    • passes: The validator script matches the configured artifact
    • passes: The transaction spends my view of the anchor UTxO
    • passes: Exactly one continuing output keeps the thread token and the locked ADA
    • passes: The new inline datum keeps this ledger’s identity
    • passes: The anchored height is at or below my tip
    • passes: Its block hash and state root equal my own block
    • passes: Its member keys and threshold equal my current ones
    • passes: At least the threshold of members are required signers
  4. Co-sign Each member signs the transaction body hash with its member key and returns the witness on ~anchor/sig. If some members stay silent for 30 seconds, the leader retries with the members that answered, provided they reach the threshold.
  5. Submit The leader assembles the witnesses, checks the transaction still hashes to what the members signed, and submits it through the node’s own transaction path. The anchor wallet pays the fee and collateral.
  6. Validate The Plutus V3 validator runs on Cardano. It checks signatures, shape, height and identity. It does not check that the state root is correct; the members did that before signing.
    • passes: The input datum has the v1 shape: eleven fields, 32-byte hashes
    • passes: At least the threshold of the input datum’s member keys signed
    • passes: Exactly one continuing output carries the thread token
    • passes: The new datum is inline, has the v1 shape, and lists 1 to 32 sorted member keys
    • passes: The height increases
    • passes: Chain id, genesis id, application id, profile and fingerprint are unchanged
    • passes: The locked ADA is not reduced
  7. Confirm Every member reads the new thread UTxO from its own Cardano view and updates its anchored height and transaction, so lagBlocks drops. The anchor/commitment endpoint then reports the root as “L1-confirmed by this node”: a verifier should still check the Cardano output itself.

What if: Metadata mode

A plain transaction carries the commitment. Cardano enforces nothing about it.

  1. Trigger The anchor leader is the node with anchor.enabled, node 0 on a local cluster. On its tick, if new blocks exist and every-blocks have accumulated since the last anchor, or max-interval-minutes have passed, it starts an advance. App ledger finality never waits for this.
  2. Build a metadata tx The leader builds a plain transaction whose metadata, under label 7014 by default, holds [chain-id, from-height, to-height, block-hash, state-root]. Only the anchor wallet signs it. There is no bootstrap, thread token, or co-signing.
  3. Submit and confirm The leader submits it and watches its own L1 sync. An anchor not seen within 2 minutes is resubmitted, and an L1 rollback that removes it puts it back in pending. Cardano stores the data but checks nothing about it, so the trust statement is weaker than in script mode.

What if: The leader forges a root

A compromised leader writes a state root no member computed.

  1. Trigger The anchor leader is the node with anchor.enabled, node 0 on a local cluster. On its tick, if new blocks exist and every-blocks have accumulated since the last anchor, or max-interval-minutes have passed, it starts an advance. App ledger finality never waits for this.
  2. Build with a forged root The leader writes a datum whose state root is 91c4…, not the root of block 42, adds its own witness, and sends the request.
  3. Refuse Members B and C compare the datum with their own block 42. The state root differs, so neither signs, and the remaining checks are not reached.
    • passes: The validator script matches the configured artifact
    • passes: The transaction spends my view of the anchor UTxO
    • passes: Exactly one continuing output keeps the thread token and the locked ADA
    • passes: The new inline datum keeps this ledger’s identity
    • passes: The anchored height is at or below my tip
    • fails: Its block hash and state root equal my own block
    • not reached: Its member keys and threshold equal my current ones
    • not reached: At least the threshold of members are required signers
  4. Stuck below threshold After 30 seconds the leader has 1 witness, its own, and the threshold is 2. The round stops and is retried on a later tick. If the leader submitted the transaction anyway, the validator would reject it: fewer than the threshold of member keys signed.
    • passes: The input datum has the v1 shape: eleven fields, 32-byte hashes
    • fails: At least the threshold of the input datum’s member keys signed
    • not reached: Exactly one continuing output carries the thread token
    • not reached: The new datum is inline, has the v1 shape, and lists 1 to 32 sorted member keys
    • not reached: The height increases
    • not reached: Chain id, genesis id, application id, profile and fingerprint are unchanged
    • not reached: The locked ADA is not reduced

What if: The leader is offline

Anchoring stops; app ledger finality does not.

  1. No anchors Node 0 is down. Members B and C are 2 of 3, so they keep finalizing blocks: finality never waits for Cardano. But nobody builds an advance, and lagBlocks climbs on every member.
  2. Move the leader Recovery is an operator action: configure anchor.*, with the wallet key, on another member and restart it. The anchor identity lives on Cardano, so the new leader resumes from the current thread UTxO.
  • anchor leader
  • co-signing member
  • Cardano
  • anchored
  • refused or stalled

The datum carries eleven fields: version, chain id, chain genesis id, application id, commitment profile id, format fingerprint, height, block hash, state root, member keys, and threshold. The validator authorizes an advance against the input datum’s members and threshold, so membership changes flow into the next datum.

The anchor wallet is a raw 32-byte Ed25519 seed (anchor.signing-key, in hex). The node derives its enterprise address and logs it at startup (anchor wallet address: addr...). Fund that address. Where /status shows it depends on the mode:

Modeanchor.addressanchor.walletAddress
metadataThe anchor walletNot present
scriptThe script address once bootstrapped; the wallet before thatThe anchor wallet, which keeps paying fees
Terminal window
# Devnet: fund an address from the self-contained faucet
curl -X POST http://127.0.0.1:7070/api/v1/devnet/fund \
-H 'Content-Type: application/json' \
-d '{"address":"addr...","ada":100}'
# Generate a dedicated seed
openssl rand -hex 32

The local cluster launcher’s anchor-bootstrap command funds the demo wallet from the devnet faucet for you.

Only the anchor leader needs anchor.* configuration — typically node 0. In script mode, members co-sign and adopt the on-chain identity with zero anchor configuration; they verify each advance against their own ledger and L1 view before signing.

The leader is the node that drives anchoring: it watches finalized progress, builds the anchor tx when it is due, pays fees and collateral from the anchor wallet, submits through the node’s own tx path, and tracks L1 confirmation.

This is a different axis from the sequencer. The proposer orders app blocks and may rotate; anchor leadership is fixed to the anchor.enabled node and does not rotate, because there is exactly one thread UTxO to spend and one wallet paying fees. Concurrent leaders would simply race on the same UTxO.

In script mode the leader has no unilateral power:

  • every advance needs threshold member co-signatures;
  • each member verifies the proposed datum against its own block at that height and its own L1 view before signing, and refuses a block hash or state root that differs from its own; and
  • the on-chain validator independently re-enforces the member threshold and increasing height.

The validator does not know the app ledger’s state, so it cannot check that a root is correct. Root correctness comes from the members who signed.

An unavailable leader can stop anchoring while app ledger finality continues. Threshold checks prevent unilateral script-state advances, but a compromised leader can also endanger its fee wallet and disrupt operations. The app ledger keeps finalizing and lagBlocks climbs visibly. Recovery is operational: enable anchor.* with the wallet key on another member and restart it. The on-chain identity is persisted on L1 and members adopt it from sign requests, so the new leader resumes where the old one stopped.

Metadata mode is the same minus co-signing. Its commitment is data-only and the leader alone signs, so the trust statement is correspondingly weaker.

yano:
app-chain:
anchor:
enabled: true
mode: metadata # or: script
signing-key: "<anchor wallet Ed25519 seed hex>" # separate from member keys
every-blocks: 10 # anchor cadence in blocks
max-interval-minutes: 60 # or after this long

Keep the anchor seed separate from member signing keys, API keys, and effect credentials. These credentials serve different roles and should have separate access controls.

What an anchor proves — and what it does not

Section titled “What an anchor proves — and what it does not”

An anchor binds a certified app ledger root to public L1 history at a Cardano-observable time. That gives an auditor an independent reference point they can check without asking any app ledger node.

It does not prove:

  • that the application’s business claim is true — only that the members committed to this exact state;
  • that the data behind a hash is still retrievable — durable availability is a separate property;
  • anything at all about heights after the anchored one.

A node-reported anchor is labelled NODE_CONFIRMED_L1_REFERENCE in proof output. That is the node’s claim about L1, not independently verified L1 truth. An independent verifier should check the Cardano output itself. See State and proofs.

On the local devnet, with no wallet or test ADA of your own:

Terminal window
export YANO_CLUSTER_DIR=/tmp/yano-anchor-try
./yano.sh appchain cluster start 3 --anchor-mode script --anchor-every 2
./yano.sh appchain cluster anchor-bootstrap orders-chain
./yano.sh appchain cluster submit orders-chain audit '{"event":"anchored","id":"A-1"}'
./yano.sh appchain cluster submit orders-chain audit '{"event":"anchored","id":"A-2"}'
curl -s http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/status \
| jq '.anchor | {bootstrapped, scriptAddress, lastAnchoredHeight, lastAnchorTx, lagBlocks}'

After about 20 seconds, bootstrapped is true, lastAnchoredHeight is above 0, and lastAnchorTx names the advance transaction. Clean up with ./yano.sh appchain cluster clean.

Tutorial 7 — anchors and verification walks through the same sequence and then verifies a record against the anchor, step by step.