7 min read
Use the CardanoWall CLI in CI/CD and Scripts
Run Proof of Existence from a script: the cardanowall CLI puts JSON on stdout, diagnostics on stderr, gives you stable verification exit codes, takes secrets safely, and publishes through any Label 309 gateway.

Reach for the cardanowall CLI when Proof of Existence has to run inside a script instead of a browser. It is built for automation: pass --json to get machine-readable data on stdout, treat stderr as diagnostics, and branch on a small set of stable exit codes. That is enough to wire it into CI/CD, release pipelines, compliance jobs, and internal audit tooling.
The CLI can verify records without a CardanoWall account, publish proofs through any Label 309 gateway, sign records locally, derive an identity from a seed, build and check Merkle proofs, and read a sealed inbox. It is the same open-source tool anyone can run, published on crates.io as the binary cardanowall — no cw alias — and the source lives at github.com/cardanowall.
Why use the CLI instead of the website?
The website is built for people. Automation needs something different:
- deterministic commands you can run unattended;
- machine-readable output;
- stable exit codes you can branch on;
- no browser session to drive;
- explicit gateway configuration instead of a logged-in account;
- secrets read from stdin, a file, or an environment variable — never typed into a prompt;
- logs you can archive alongside the job.
That is exactly what the CLI gives you, and it keeps the proof close to the system that actually produces the evidence.
How do I verify a proof in a script?
Verification is the easiest command to automate, and the most useful one to run anywhere:
cardanowall verify <tx-hash> --jsonVerifying needs no CardanoWall account and no gateway operator. It reads the transaction metadata from a public Cardano explorer (Koios- or Blockfrost-compatible), optionally fetches content from public Arweave or IPFS gateways, validates the record, and checks any signatures. That is the whole point of the standard: a proof you verified once stays independently verifiable by anyone, with no trust in the issuer, their domain, or their server.
For tighter control, point it at your own data source and set a confirmation depth:
cardanowall verify <tx-hash> \
--cardano-gateway https://api.koios.rest/api/v1 \
--threshold 20 \
--jsonData goes to stdout and diagnostics go to stderr, so command composition stays clean:
if cardanowall verify "$TX_HASH" --json > verify-report.json; then
echo "proof verified"
else
code=$?
echo "verification did not pass, exit code: $code" >&2
fiFor a deeper look at what a verifier actually checks, see how to verify a Label 309 record.
What do the exit codes mean?
Branch on the exit code instead of parsing the human-readable text — the code already tells you the class of result:
| Code | Meaning |
|---|---|
0 | valid / success |
1 | integrity-class failure (a cryptographic or structural check failed) |
2 | network-class failure or an unverifiable result (a fetch or transport error) |
3 | pending — usually insufficient confirmations |
4 | CLI input error (bad arguments or missing required input) |
For verify, codes 0–3 map straight through from the verifier's verdict — valid, failed, unverifiable, and pending respectively — so the same contract holds whether you read the exit code or the --json report.
That gives automation a clean policy:
0: continue.1: fail the job — the proof or content check failed.2: retry, or mark the run inconclusive.3: wait and retry after more confirmations.4: fix the script or its inputs.
In a release workflow, do not treat pending as success. A pending proof may settle later, but it is not yet final.
How do I publish a proof from automation?
Publishing needs a gateway, because it submits a real Cardano transaction and draws on your gateway balance to pay the transaction fee and any Arweave storage. (For why publishing costs anything at all, see why publishing has a price.)
Hash and anchor a file:
cardanowall submit \
--file ./release-manifest.json \
--base-url https://your-gateway.example \
--api-key "$CARDANOWALL_API_KEY" \
--jsonAnchor a precomputed digest:
cardanowall submit --hash <64-hex-digest> --jsonAnchor a Merkle batch built from a list of leaves:
cardanowall submit --merkle ./leaves.txt --jsonThese three modes map cleanly onto common automation patterns. A small job can anchor a single file. A release system can anchor a precomputed manifest digest. A high-volume system can fold many item hashes into one Merkle root and publish a single record. The first run-through is covered in publish your first proof.
Run cardanowall <command> --help for the authoritative, version-matched flag list — that is always the final word on options for your installed build.
How do I configure gateways once instead of per job?
Hardcoding a gateway URL and API key into every job is brittle. The CLI stores named gateway profiles instead:
cardanowall gateway add prod --base-url https://your-gateway.example
cardanowall gateway use prod
cardanowall gateway listIn CI, feed the key from stdin rather than answering an interactive prompt:
cardanowall gateway add prod \
--base-url https://your-gateway.example \
--api-key-stdin < ./gateway-api-key.txtThe service gateway resolves in a fixed order: an explicit flag wins, then the environment variable, then the active profile. That split suits both environments — a developer can lean on a saved profile, while CI injects CARDANOWALL_BASE_URL and CARDANOWALL_API_KEY and skips the config file entirely.
How do I keep secrets out of the command line?
This is the easy mistake to make, so make it hard to make. Never put a raw Identity Seed or recipient key directly in command arguments — argv leaks into shell history, CI logs, and process listings (ps).
Use a safe input source instead:
--seed-stdinor--seed-file;--secret-key-stdinor--secret-key-file;- an environment variable, only when your CI secret handling is clean.
For example, pipe the seed in on stdin:
printf '%s' "$CARDANOWALL_SEED" | \
cardanowall submit --file ./manifest.json --seed-stdin --jsonA secret must come from exactly one source; supplying it from two at once (say a stale --seed-file plus CARDANOWALL_SEED) is a hard input error, so an old value can never silently shadow the one you meant. The raw --seed and --secret-key argv flags still exist for throwaway test values, but they print a one-line warning on stderr when used. Treat that warning as a real control, not noise. And because keys never need to travel to a server to do this work, the seed stays on the machine that runs the command — the principle behind why keys never leave the device.
How do I sign a record in a pipeline?
A signed record proves not just that content existed, but that a specific project or organization key vouched for it. Authorship signatures are always optional in Label 309 — a hash-only proof is just as valid — but they are useful when provenance matters.
For straightforward local signing, attach the seed safely on the publish itself:
cardanowall submit --file ./release.json --seed-stdin --jsonFor stricter environments, keep the key off the build host entirely and sign in two steps:
cardanowall sign prepare --signer-pubkey <hex> --hash <hex>
cardanowall sign assemble --signer-pubkey <hex> --signature <hex> --in record.cborThat split lets a KMS, an HSM, an offline workstation, or a protected signing service produce the Ed25519 signature, while the automation pipeline handles record assembly and publishing without ever touching the private key.
How do I batch thousands of artifacts into one record?
Merkle roots are how a CLI workflow scales. Instead of one transaction per artifact, build a leaf list and anchor the root:
cardanowall merkle build --in leaves.txt --json > merkle.json
cardanowall submit --merkle ./leaves.txt --jsonLater, prove a single leaf with its inclusion proof:
cardanowall merkle verify \
--root <hex32> \
--leaf <hex32> \
--proof proof.jsonThis is a strong fit for CI/CD release evidence, batches of AI-generated content, dataset manifests, compliance snapshots, log archives, and media provenance packages. Keep the leaf list and the inclusion proofs with the rest of your release evidence: the on-chain root is only half of the later verification story. The pattern is covered in depth in one record for thousands of files, and the CI angle in proofs in your CI/CD pipeline.
When should automation handle sealed records?
The CLI can also work with sealed PoE — encrypted records addressed to one or more recipients:
cardanowall inbox sync --seed-stdin
cardanowall inbox list --seed-stdin --json
cardanowall inbox decrypt <tx-hash> --secret-key-stdinRun these only on machines authorized to hold the recipient key. Do not drop a recipient key into a general CI environment unless that job is explicitly part of the recipient's trusted processing boundary. Decrypting also produces plaintext: a process that can decrypt can, in principle, leak what it decrypts, so place that step where the key already belongs.
For most public automation you will verify signed or hash-only proofs and never touch a recipient key. Reach for sealed-inbox commands when you are building private evidence intake, confidential delivery, or controlled archival.
What should a CI job archive?
Archive the proof report, not just the transaction hash. A good run preserves:
- the CLI version;
- the command arguments, with secrets redacted;
- the input manifest or file digest;
- the transaction hash;
- the
--jsonpublish output; - the
--jsonverification report; - the Merkle leaf list and inclusion proofs, if used;
- the signer identity of any signed record;
- retry logs for pending or unverifiable results.
That gives future auditors enough context to reproduce the check without anyone reconstructing how a release job was wired months earlier.
A practical release pattern
A release job can take this shape:
- Build the artifacts.
- Generate checksums, SBOMs, attestations, and a release manifest.
- Hash the manifest, or build a Merkle leaf list.
- Publish with
cardanowall submit. - Capture the transaction hash and wait for confirmations.
- Run
cardanowall verify. - Store the JSON reports with the release artifacts.
- Fail the release on
failed; retry or hold onpendingandunverifiable.
None of that is complicated, and that is the point. The proof should be boring enough to run on every release.
Further reading
- Build on the CardanoWall API — when a script is not enough and you want to drive the gateway directly.
- The open-source CLI and SDKs: github.com/cardanowall
- The Label 309 standard: label309.org