Twelve weeks ago Ligate Chain was a specification document and a
Cargo workspace with the Sovereign SDK pinned in Cargo.toml.
Today it has a runtime, a Celestia DA adapter, a Risc0 zkVM, real
$LGT transfers through sov-bank, a checked-in devnet config,
and a node binary that boots.
This post is the changelog for Phase A. The first half is what actually got built and why each piece matters. The second half is the architecture, for readers who want to understand how the chain fits together.
What shipped
Phase A was about turning the protocol spec into a chain that runs.
Six sub-phases, all merged to main:
- A.1 — Scaffolded
ligate-stf, the runtime crate that composes our modules into a state-transition function. - A.2.1 — Upgraded the Sovereign SDK pin to its post-rebrand release. This was the largest single migration: state APIs, module trait shape, authenticator pattern all changed.
- A.2.2 — Rebuilt the rollup binary on the new SDK, including
a working
RollupAuthenticatorand thecelestia-rollupruntime. - A.2.3 — Checked in the devnet genesis files (bank, attestation, attester incentives, prover incentives, sequencer registry, operator incentives) so anyone with the repo can boot a local devnet.
- A.2.4 — Made the chain hash derivable from the runtime schema at build time, so consensus-critical hashes can't drift from the code without breaking the build.
- A.3 — Wired the Celestia DA adapter. The rollup now posts blocks to Celestia for data availability and reads them back through the same path a third-party light client would.
- A.4 — Landed the Risc0 inner zkVM with a Celestia guest
binary, plus the
lig1Bech32 devnet address scheme.
Two integration changes worth flagging on their own:
sov-bankis wired. Fee accounting is no longer a counter: it's a real token transfer. The 0.001$LGTyou pay to submit an attestation now moves between balances in the bank module, the schema owner gets their fee-routing share (up to 50% per the schema config), and the protocol treasury gets the rest. The same wiring carries over to the production token model.- Devnet addresses are
lig1.... The chain's accounts are identified by Bech32m strings with theligHRP, the same way Cosmos chains usecosmos1...and Celestia usescelestia1.... Schemas arelsc1..., attestor sets arelas1..., public keys arelpk1..., payload hashes arelph1.... The address scheme is documented end-to-end in docs/protocol/addresses-and-signing.md.
Why this is a milestone
The ZK prover landing is the gate.
Until A.4, "Ligate Chain" was a runtime that could be tested in Rust. After A.4, it's a runtime that can produce zero-knowledge proofs of correct execution against a real DA layer. That's the difference between an app-chain demo and a credible rollup.
Two consequences flow from this.
The first is that the chain repo is now ready to flip public. We held it private through Phase A on the theory that a half-finished SDK migration is a worse first impression than no repo at all. The ZK piece was the last load-bearing thing missing. With A.4 merged and the branch tree cleaned up, there's nothing embarrassing left.
The second is that the devnet target stops being abstract. Q2 2026 was always the target on the roadmap, but it was a date attached to a vision document. With Phase A merged, the path to devnet is a finite list of remaining issues, not a research project.
What's next
Phase B is partner integration. The protocol works end-to-end as of A.4, which means the next category of work is no longer "make the chain run" but "make the chain useful to someone outside this repo." Concretely:
- Themisra Proof-of-Prompt schema crate, rebased on the new SDK.
We landed an early version of this on
chain/themisra-popbefore the SDK upgrade, then closed the PR rather than carry the rebase cost through the migration. It comes back as the canonical example of a third-party schema crate. - Iris MCP server + relayer, in a separate repo. The relayer
signs and submits attestations on behalf of an AI agent that
doesn't hold
$LGT. This is the integration that proves the protocol economic model from the consumer side. - Public devnet faucet, explorer, RPC endpoint. Q2 2026 target.
We're also looking for one or two design partners willing to register a real schema and run a real attestor set against the Phase A chain. If you're building anything in AI provenance, content authenticity, agent accountability, or compliance attestation and you want to talk: hello@ligate.io.
Architecture: how the pieces fit
The remainder of this post is for readers who want to understand the chain's internal structure. If you only wanted the headline, you have it.
The Sovereign SDK foundation
Ligate Chain is a sovereign rollup, in the Celestia sense of the word: a chain that has its own validator set and its own state, but outsources data availability to Celestia rather than running its own DA layer or settling to Ethereum.
We use the Sovereign SDK as the runtime framework. The SDK gives us:
- A module system (
Moduletrait,CallMessage, state accessors). - A state-transition function harness (
StateTransitionFunction). - DA layer abstractions, with adapters for Celestia, Avail, and mock DAs.
- ZK prover plumbing, with adapters for Risc0 and SP1.
- An authenticator pattern that lets the chain accept multiple signature schemes at the runtime boundary.
Our job is to compose these primitives into a chain that does attestations specifically, not to reinvent the framework.
The runtime crate (ligate-stf)
crates/stf is the crate where everything composes. It declares
the runtime as a tuple of modules:
pub struct Runtime<S: Spec> {
pub bank: Bank<S>,
pub sequencer_registry: SequencerRegistry<S>,
pub attester_incentives: AttesterIncentives<S>,
pub prover_incentives: ProverIncentives<S>,
pub operator_incentives: OperatorIncentives<S>,
pub accounts: Accounts<S>,
pub attestation: Attestation<S>,
}
The runtime is also where we wire runtime_capabilities, which tells
the SDK how to dispatch incoming transactions, how to authenticate
them, and how to charge fees. The build.rs derives CHAIN_HASH from
the runtime's compile-time schema so any divergence between code and
genesis fails the build, not consensus.
The attestation module
The protocol logic lives in crates/modules/attestation. It exposes
three call messages:
pub enum CallMessage {
RegisterAttestorSet { members: Vec<PublicKey>, threshold: u8 },
RegisterSchema { name: String, version: u32, attestor_set: AttestorSetId, fee_routing_bps: u16, fee_routing_addr: Address, payload_shape: SchemaPayload },
SubmitAttestation { schema_id: SchemaId, payload_hash: Hash, signatures: Vec<Signature> },
}
SubmitAttestation enforces three invariants on-chain:
- The supplied
schema_idexists. - The signatures meet the threshold of the schema's bound attestor set.
- The
(schema_id, payload_hash)pair has not been written before (write-once replay protection).
Everything else, including the field-level semantics of the payload, is enforced off-chain in the schema's reference SDK. The chain never sees the prompt or the model output. It sees a hash and the signatures.
This narrowness is deliberate. The chain is a registry plus a write-once ledger of (schema, hash, signers) tuples. Most of what makes a given schema useful is in the SDK and the verifier tooling that surrounds it, not in chain consensus.
The Celestia DA adapter
crates/rollup/src/celestia_rollup.rs glues the runtime to Celestia.
Three pieces:
- Block submission — the sequencer batches transactions, encodes them as Celestia blob namespaces, and pays the Celestia gas to publish them.
- Block ingestion — the rollup reads from a Celestia light client, filters blobs by namespace, and feeds them into the STF in order.
- Light client safety — the DA layer's finality determines the rollup's finality. We don't have our own DA fault-tolerance to worry about; we inherit Celestia's.
The devnet config in devnet/celestia.toml and devnet/rollup.toml
sets the Celestia network endpoint, the namespace, and the rollup
genesis. With those committed, cargo run --bin ligate-node against a
local Celestia mocha-testnet boots a working rollup.
The Risc0 inner zkVM
The new piece in A.4. Lives at crates/rollup/provers/risc0/.
A rollup needs a way to convince third parties that its state transitions are correct without making them re-execute every transaction. The standard answer is a zero-knowledge proof. Risc0 is a general-purpose zkVM: you compile a Rust program to a RISC-V guest binary, run it inside the Risc0 prover, and get back a SNARK that attests to the binary's correct execution.
For Ligate, the guest binary is guest-celestia/src/bin/rollup.rs.
It runs the entire STF, including DA verification and module
state transitions, inside the zkVM. The output is a proof that says,
roughly: "given this Celestia DA root and this prior state root,
this batch of transactions produces this new state root."
We picked Risc0 over the SP1/Jolt/Nexus alternatives for three reasons. First, the Sovereign SDK's Risc0 adapter is the most mature. Second, Risc0's STARK-to-SNARK wrapping is solid enough that on-chain verification cost is bounded. Third, the developer ergonomics of "compile your normal Rust into a guest binary" beat the alternative of writing circuit code by hand. We can revisit the choice later if benchmarks favor SP1, but for v0 the Risc0 path is the lowest-risk one.
Address scheme: lig1...
We use Bech32m encoding with chain-specific human-readable prefixes:
| Prefix | What it identifies |
|---|---|
lig1 | Account address (28 bytes) |
lpk1 | Public key (ed25519) |
lsc1 | Schema ID |
las1 | Attestor set ID |
lph1 | Payload hash (sha256 of borsh body) |
Bech32m gives us address checksums, mixed-case rejection, and clean copy-paste behavior. Every distinct kind of identifier gets its own HRP so a schema ID can never be mistaken for an account, and an attestor set ID can never be mistaken for a payload hash. Eyeballing catches errors before the verifier does.
The chain accepts ed25519 signatures only in v0. EVM secp256k1 signing is tracked as a separate piece of work (issue #72) because it needs a second authenticator and a second address derivation path. Until that lands, MetaMask cannot sign Ligate transactions, and we say so plainly in the docs rather than letting users hit a confusing "signature invalid" error and assume the chain is broken.
Fees and sov-bank
Every fee-bearing call (RegisterSchema, RegisterAttestorSet,
SubmitAttestation) is paid in $LGT through sov-bank. The flow:
- The transaction arrives signed by a
lig1account holding$LGT. - The authenticator deducts the fee from that account's bank balance.
- The handler runs. If the schema configures
fee_routing_bps, that share of the fee routes tofee_routing_addr(capped at 50%). - The remainder credits the protocol treasury account.
This is real sov-bank, not a counter. Balances are queryable via
RPC, transferable across accounts, and they are the same balances
the sequencer and the prover get paid out of. When $LGT becomes a
real token at devnet launch, we don't have to rewrite the fee logic.
If you're a developer who wants to read the code: the chain repo
flips public shortly after this post (probably this week). When
it does, docs/protocol/attestation-v0.md is the right place to
start, followed by crates/modules/attestation/src/lib.rs and
crates/stf/src/lib.rs.
If you're a regulator, an audit firm, an AI provider, or a standards body and you'd like to register a schema or run an attestor set against this protocol, hello@ligate.io.