VI·SOFT
← Back to work
cartridgehttps://cartridge.gg/

Saya - Settlement Orchestrator

The orchestrator that settles Starknet L3 appchains to L2 - speaking STARK proofs, TEE attestations, and pure data-availability publishing through one operator-facing deamon.

RustSTARKTEECelestiaCairoStarknetrollups
§ 01

Saya is the settlement orchestrator we built for Starknet-based L3 appchains. It watches an appchain's sequencer, takes finalized blocks, and settles them — where "settles them" isn't one operation. Depending on how a given appchain is configured, that means generating a full STARK proof and posting it to a Starknet L2 verifier contract, generating a lighter TEE-backed attestation instead, or skipping onchain settlement entirely and posting to a data-availability layer. One daemon, three fundamentally different trust models, one operator-facing CLI.

Why three binaries, not three flags

The natural first instinct is a single binary with a `--mode` flag. We didn't ship that, and the reason is dependency weight rather than taste. The STARK proving path pulls in a proving toolchain heavy enough that it started producing real, blocking version conflicts with dependencies the TEE path needed for its own attestation libraries — the kind of conflict where two transitive dependencies each pin an incompatible version of the same crate and there's no version that satisfies both. We hit this for real during development, not hypothetically, and the fix that actually held up was structural: separate Cargo workspaces per trust model, compiled and shipped as separate binaries, sharing only the parts of the core library that don't care which settlement path is active.

That separation paid for itself again at the Docker layer. The TEE path depends on a private, access-gated repository for its attestation client. Early on, that dependency leaked into the shared core library, which meant the STARK-only Docker build — used far more often, by people who have no reason to need TEE credentials — suddenly required access it shouldn't have needed. Once we noticed, we pushed the TEE-specific dependency out of the shared core and behind the workspace boundary that was already keeping the two proving paths apart.

Designing for a step that's allowed to fail expensively

The TEE settlement path runs a zero-knowledge proof over the attested execution, and that proof generation step is slow — on the order of ten minutes per batch. That changes how you're allowed to handle failure. A crash midway through a STARK proof that takes seconds to redo is a non-event; a crash midway through a ten-minute proof is an incident, because retrying blind means potentially burning another ten minutes — and real proving-service quota — on a batch that might fail the same way again.

We built crash recovery around a small local database that checkpoints progress through the pipeline, so a restart resumes from the last completed stage instead of from zero. The harder design question was what to do when a batch fails on its own merits, not because of a crash: retry it automatically, or stop and surface it. We chose to fail fast. A batch that's broken for a structural reason will fail the same way on a second attempt, and a bounded-retry policy mostly just guarantees you pay for that failure twice before a human finds out about it.

What stayed constant across all three modes

Whatever the settlement destination, the operator-facing behavior is identical: point the daemon at an appchain, tell it which mode to run in, and it handles the rest. The complexity of three trust models is real, but it's contained entirely inside the boundary between binaries — it never becomes the operator's problem to reason about which proving system is doing what underneath a given deployment.