Operator and builder CLI for Ligate Chain. One-line wrapper around the typed client SDK: generate keys, query balances, transfer $LGT, drip from the faucet, register schemas / attestor sets, submit attestations.
Two paths, in preference order.
Each tagged release (v* tag) attaches platform tarballs for linux-amd64, linux-arm64, darwin-arm64, each with a SHA-256 sidecar. (Intel Mac / darwin-amd64 was removed from the release matrix in v0.1.0-devnet prep; fall back to cargo install --git on Intel.) Pick the one for your host:
# Replace VERSION with the release tag (e.g. v0.2.1) and
# PLATFORM with one of: linux-amd64, linux-arm64, darwin-arm64.
# (Intel Mac is not currently built; see Status section.)
curl -L -o ligate.tar.gz \
https://github.com/ligate-io/ligate-cli/releases/download/VERSION/ligate-cli-VERSION-PLATFORM.tar.gz
tar -xzf ligate.tar.gz
sudo mv ligate /usr/local/bin/
ligate --helpNo Rust toolchain needed. ~30s end-to-end on a normal connection.
Fallback when a tagged tarball isn't published for your platform, or you want to track main ahead of a release.
# Linux: system libclang is required for the chain's transitive
# librocksdb-sys build script (bindgen needs libclang.dylib).
sudo apt install -y libclang-dev clang
# macOS: xcode-select --install (the bundled clang is enough)
# Expect 10–15 min cold-cache, ~5 min warm. Pulls ~3 GiB of git
# deps (chain runtime + SDK fork). Subsequent builds reuse the
# Cargo cache.
SKIP_GUEST_BUILD=1 RISC0_SKIP_BUILD_KERNELS=1 \
cargo install --git https://github.com/ligate-io/ligate-cliThe two env vars skip the risc0 zkVM guest compile — the CLI doesn't run real proving (mock zkvm is fine for client-side use). Without them, you'd also need the risc0 toolchain (rzup install).
crates.io publish is blocked until the Sovereign SDK lands there (chain pin is a git dep today). Tracked in ligate-chain#235. Cargo.toml also sets publish = false so an accidental cargo publish is a no-op until both are resolved.
# Generate a keypair (writes to OS keystore dir, mode 0600)
ligate keys generate --name alice
# Show your address
ligate keys show alice
# Claim a one-shot drip from the public devnet faucet
ligate faucet $(ligate keys show alice)
# Check the balance arrived
ligate balance $(ligate keys show alice) --token-id <hex>
# Send some $LGT to another address
ligate transfer \
--to lig1xyz... \
--amount 0.5 \
--signer alice \
--chain-id 1 \
--chain-hash <64-char-hex> \
--token-id <64-char-hex>All chain-identity flags also accept env vars (LIGATE_RPC, LIGATE_CHAIN_ID, LIGATE_CHAIN_HASH, LIGATE_LGT_TOKEN_ID). Set them once in your shell and the flags become optional.
ligate completions <SHELL> prints a completion script to stdout. Pipe to the install path for your shell:
# zsh (user-level)
ligate completions zsh > ~/.local/share/zsh/site-functions/_ligate
# bash (system-wide)
ligate completions bash | sudo tee /etc/bash_completion.d/ligate
# fish (user-level)
ligate completions fish > ~/.config/fish/completions/ligate.fish
# powershell — append to your $PROFILE
ligate completions powershell >> $PROFILEThen ligate <TAB> discovers subcommands; ligate keys <TAB> discovers their sub-subcommands; flags tab-complete too.
Devnet. ligate-devnet-1 is live.
v0 surface (info, keys, balance, transfer, faucet, register-attestor-set, register-schema, sign-attestation, submit-attestation, query, completions) is wired and CI-green against ligate-chain main. ligate node start is not in scope for v0.
First tagged release is v0.1.0-devnet — cut alongside ligate-chain v0.1.0-devnet.
Current release: v0.2.1 (see Cargo.toml for the live version).
Going forward, releases drop the -devnet suffix per the convention adopted in ligate-chain#374. Plain semver tags (v0.1.x, v0.2.x, ...) for the binary, with network identity carried in chain_id and genesis directory names instead of the binary tag. Past -devnet tags stay as archaeology. The next CLI cut (v0.1.3 or higher) will be the first to land on the new scheme alongside ligate-chain v0.1.3.
One-line chain-identity check for the configured RPC. Prints chain_id, chain_hash, and the node version reported by /v1/rollup/info. No signing, no keystore touched.
ligate info
ligate info --json # for piping into jq
Useful first command in the post-ligate-node-up smoke test from docs/development/public-devnet-deploy.md. Exports cleanly via export LIGATE_CHAIN_HASH=$(ligate info --json | jq -r .chain_hash).
Local Ed25519 keystore management. Files are written to the OS-default data dir (~/Library/Application Support/io.ligate.cli/keys on macOS, $XDG_DATA_HOME/cli/keys on Linux; the directories crate uses the last ProjectDirs::from component as the leaf), with <role>.key (mode 0600) and <role>.address plaintext.
ligate keys generate --name alice [--output PATH]
ligate keys list [--keystore PATH]
ligate keys show alice [--keystore PATH]
ligate keys show alice --pubkey [--keystore PATH] # bech32m lpk1... for register-attestor-set
generate also prints the bech32m lpk1... pubkey alongside the lig1... address, so registering the role as an attestor right after keys generate is a copy-paste away.
The on-disk format matches ligate-genesis-tool keys generate from the chain repo. Keystores produced by either tool are interchangeable.
Read-only $LGT balance query.
ligate balance lig1xyz... --token-id <hex>
ligate balance lig1xyz... --token-id <hex> --json
Build a bank.transfer, sign it against the chain hash, submit via ligate-client::submit::Submitter.
ligate transfer \
--to lig1... \
--amount 1.0 \ # OR --amount-nano 1000000000
--signer <role> \
--chain-id <u64> \
--chain-hash <64-hex> \
--token-id <64-hex> \
[--max-fee 100000000]
Claim a drip from the public devnet faucet. Defaults to https://api.ligate.io/v1/drip; override for a self-hosted instance.
ligate faucet lig1xyz...
ligate faucet lig1xyz... --faucet-url https://api.ligate.io/v1/drip
Register a quorum of attestor public keys plus an M-of-N threshold. The on-chain id is derived deterministically from the sorted member list, so submitting the same set twice is a no-op.
ligate register-attestor-set \
--members lpk1...,lpk1...,lpk1... \ # comma-separated, order-independent
--threshold 2 \ # 1..=members.len()
--signer <role> \
--chain-id <u64> --chain-hash <64-hex>
Returns the bech32m las1... set id on stdout (use --json for structured output).
Register an attestation schema from a JSON definition file. The schema name + version + attestor-set id collectively make up the on-chain key; bumping version is how a schema rotates to a new attestor set.
ligate register-schema \
--file ./themisra-proof-of-prompt-v1.json \
--signer <role> \
--chain-id <u64> --chain-hash <64-hex>
Schema JSON shape (name, version, attestor_set_id, plus optional fee_routing_bps / fee_routing_addr / payload_shape_hash) is documented in the module docstring at src/register_schema.rs. Returns the lsc1... schema id.
The attestor half of the attestation flow. Produces one entry of the signatures array that submit-attestation --signatures consumes. Run on each attestor's machine; the submitter concatenates the outputs into a multi-entry array before submission.
ligate sign-attestation \
--schema lsc1... \ # registered schema id
--payload-hash lph1... \ # OR --payload-file <path> to SHA-256 a canonical payload
--submitter lig1... \ # the address that will run submit-attestation
[--timestamp 0] \ # default 0 (chain v0 hardcodes timestamp=0)
--signer <attestor-role> \ # the attestor's keystore role
[--output sigs.json] \ # write the array form; default stdout
[--json]
The chain re-derives the digest at submission time using the same (schema_id, payload_hash, submitter, timestamp) tuple. If your inputs here don't match the eventual submit-attestation's --signer address, the signature won't verify. The chain's error response now includes the digest it computed and the submitter address it used, so debugging a mismatch is direct.
For the canonical byte layout (and a test vector), see docs/protocol/attestation-v0.md §wire-format in the chain repo.
Submit a threshold-signed attestation under an existing schema. Signatures are collected off-chain (one per attestor) and passed in as a JSON file.
ligate submit-attestation \
--schema lsc1... \ # registered schema id
--payload-hash lph1... \ # bech32m hash of the off-chain payload
--signatures ./sigs.json \ # array of { pubkey: lpk1..., sig: hex }
--signer <role> \
--chain-id <u64> --chain-hash <64-hex>
The chain verifies signature count meets the schema's attestor-set threshold and that every signature is from a member of that set.
Read-only fetch by id; no signing or keystore touch. Mirrors the three /v1/... REST routes the chain exposes.
ligate query schema lsc1... # registered schema
ligate query attestor-set las1... # registered attestor set
ligate query attestation lat1... # attestation (single bech32m id)
| Flag | Env | Default | Notes |
|---|---|---|---|
--rpc URL |
LIGATE_RPC |
https://rpc.ligate.io |
Target node REST endpoint |
--json |
n/a | off | Emit JSON instead of human text |
RUST_LOG |
env only | warn |
tracing filter |
flowchart LR
Op[Operator / Dev]
CLI[ligate CLI]
Keystore[(Local keystore<br/>~/.../keys/*.key)]
Faucet[Faucet service]
SDK[ligate-client<br/>typed builder + Submitter]
Node[ligate-node<br/>REST API]
Op -->|subcommand| CLI
CLI -->|read key| Keystore
CLI -->|"faucet ..."| Faucet
CLI -->|"balance / transfer"| SDK
SDK -->|"sign + submit"| Node
Faucet -->|"drip tx"| Node
keys generate lifts the chain genesis-tool's keystore logic byte-for-byte. transfer mirrors the faucet's signer pipeline (one-shot per invocation, fetches nonce from chain). balance is a pure read against NodeClient::get_balance_for_holder. faucet is just an HTTP client to the unified API's POST /v1/drip endpoint.
- Different release cadence (operator-driven, faster than chain releases).
- Different scaling concerns (zero, it's a CLI tool with a stable v0 surface).
- Same pattern as
ligate-explorer. - Versioned via crates.io once mature; pre-1.0 distributed as
cargo install --git ....
cargo build # compile
cargo run -- --help
cargo fmt
cargo clippy --all-targets -- -D warningsThe cargo test job is currently disabled in CI because the chain's risc0 prover crate (transitively pulled via ligate-rollup) trips its build script under cargo test even with SKIP_GUEST_BUILD=1. Pure-keystore unit tests live under mod tests in keys.rs and config.rs and run via cargo check --all-targets.
.pre-commit-config.yaml runs cargo fmt --check on every commit so formatting drift is caught locally instead of in CI. One-time setup per clone:
brew install pre-commit # or: pip install pre-commit
pre-commit install # writes .git/hooks/pre-commitSkip the hook for an emergency commit with git commit --no-verify; the same check still re-runs in CI.
- Tracking:
ligate-chain#112 - Chain SDK:
ligate-io/ligate-chain/crates/client-rs - Drip endpoint:
ligate-io/ligate-api(POST /v1/drip) - Genesis-tool keys:
ligate-io/ligate-chain/crates/genesis-tool
Apache-2.0 OR MIT, at your option. See LICENSE-APACHE and LICENSE-MIT.